Separate name steps for shared resources and app resources (#668)

This commit is contained in:
Matthew Fisher 2024-04-04 14:43:58 -07:00 коммит произвёл GitHub
Родитель 473564550c
Коммит 3093986dad
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 148 добавлений и 164 удалений

Просмотреть файл

@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp } from "@microsoft/vscode-azext-utils";
import { ext } from "../../../extensionVariables";
import { localize } from "../../../utils/localize";
import { ContainerAppNameStep } from "../../createContainerApp/ContainerAppNameStep";
import { ImageNameStep } from "../../image/imageSource/buildImageInAzure/ImageNameStep";
import { type DeployWorkspaceProjectInternalContext } from "./DeployWorkspaceProjectInternalContext";
/** Names the resources unique to the individual app: `container app`, `image name` */
export class AppResourcesNameStep extends AzureWizardPromptStep<DeployWorkspaceProjectInternalContext> {
public async configureBeforePrompt(context: DeployWorkspaceProjectInternalContext): Promise<void> {
if (context.newContainerAppName || context.containerApp) {
// This ensures image naming even when all other resources have already been created
context.imageName = ImageNameStep.getTimestampedImageName(context.newContainerAppName || nonNullValueAndProp(context.containerApp, 'name'));
}
}
public async prompt(context: DeployWorkspaceProjectInternalContext): Promise<void> {
context.newContainerAppName = (await context.ui.showInputBox({
prompt: localize('containerAppNamePrompt', 'Enter a name for the new container app'),
validateInput: (name: string) => ContainerAppNameStep.validateInput(name),
asyncValidationTask: async (name: string) => {
const resourceGroupName: string = context.resourceGroup?.name || nonNullProp(context, 'newResourceGroupName');
const isAvailable: boolean = await ContainerAppNameStep.isNameAvailable(context, resourceGroupName, name);
return isAvailable ? undefined : localize('containerAppExists', 'The container app "{0}" already exists in resource group "{1}".', name, resourceGroupName);
}
})).trim();
context.imageName = ImageNameStep.getTimestampedImageName(context.newContainerAppName);
ext.outputChannel.appendLog(localize('usingContainerAppName', 'User provided the name "{0}" for the new container app.', context.newContainerAppName));
}
public shouldPrompt(context: DeployWorkspaceProjectInternalContext): boolean {
return !context.containerApp && !context.newContainerAppName;
}
}

Просмотреть файл

@ -1,156 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ResourceGroupListStep } from "@microsoft/vscode-azext-azureutils";
import { AzureWizardPromptStep, nonNullProp } from "@microsoft/vscode-azext-utils";
import { ProgressLocation, window } from "vscode";
import { ext } from "../../../extensionVariables";
import { localize } from "../../../utils/localize";
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 DeployWorkspaceProjectInternalContext } from "./DeployWorkspaceProjectInternalContext";
const resourceGroupSuffix: string = '-rg';
const managedEnvironmentSuffix: string = '-env';
const logAnalyticsSuffix: string = '-log';
export const containerAppSuffix: string = '-ca';
export class DefaultResourcesNameStep extends AzureWizardPromptStep<DeployWorkspaceProjectInternalContext> {
public async configureBeforePrompt(context: DeployWorkspaceProjectInternalContext): Promise<void> {
if (context.registry && context.containerApp) {
// This ensures image naming even when all other resources have already been created
context.imageName = ImageNameStep.getTimestampedImageName(context.containerApp.name);
}
}
public async prompt(context: DeployWorkspaceProjectInternalContext): Promise<void> {
const resourceName: string = (await context.ui.showInputBox({
prompt: localize('resourceBaseNamePrompt', 'Enter a name for the new container app resource(s).'),
validateInput: (name: string) => this.validateInput(context, name),
asyncValidationTask: (name: string) => this.validateNameAvailability(context, name)
})).trim();
ext.outputChannel.appendLog(localize('usingResourceName', 'User provided the new resource name "{0}" as the base for resource creation.', resourceName))
const { resourceGroupName, logAnalyticsWorkspaceName, managedEnvironmentName, containerAppName } = await this.getResourceNames(context, resourceName);
!context.resourceGroup && (context.newResourceGroupName = resourceGroupName);
!context.managedEnvironment && (context.newLogAnalyticsWorkspaceName = logAnalyticsWorkspaceName);
!context.managedEnvironment && (context.newManagedEnvironmentName = managedEnvironmentName);
!context.containerApp && (context.newContainerAppName = containerAppName);
context.imageName = ImageNameStep.getTimestampedImageName(context.containerApp?.name || nonNullProp(context, 'newContainerAppName'));
}
public shouldPrompt(context: DeployWorkspaceProjectInternalContext): boolean {
return (!context.resourceGroup && !context.newResourceGroupName) ||
(!context.managedEnvironment && !context.newManagedEnvironmentName) ||
(!context.containerApp && !context.newContainerAppName) ||
!context.registry;
}
private validateInput(context: DeployWorkspaceProjectInternalContext, name: string = ''): string | undefined {
name = name.trim();
// ** Sorted from _most_ to _least_ strict naming rules, with priority being most strict character length since that usually gets checked first **
// ** This sorting should help to minimize the possibility of conflicting validate input error messages which could be confusing for users **
if (!context.managedEnvironment && !context.newManagedEnvironmentName) {
const result = ManagedEnvironmentNameStep.validateInput(name);
if (result) {
return result;
}
}
if (!context.containerApp && !context.newContainerAppName) {
const result = ContainerAppNameStep.validateInput(name);
if (result) {
return result;
}
}
if (!context.registry) { // Skip checking newRegistryName since it gets set every time validateNameAvailability is run
// No symbols are allowed for ACR names
const nameWithoutSymbols: string = name.replace(/[^a-z0-9]+/g, '');
const result = RegistryNameStep.validateInput(nameWithoutSymbols);
if (result) {
return result;
}
}
return undefined;
}
protected async validateNameAvailability(context: DeployWorkspaceProjectInternalContext, name: string): Promise<string | undefined> {
return await window.withProgress({
location: ProgressLocation.Notification,
cancellable: false,
title: localize('verifyingAvailabilityTitle', 'Verifying resource name availability...')
}, async () => {
const resourceNameUnavailable: string = localize('resourceNameUnavailable', 'Resource name "{0}" is unavailable.', name);
if (context.registry) {
// Skip check, one already exists so don't need to worry about naming
} else {
context.newRegistryName = await RegistryNameStep.tryGenerateRelatedName(context, name);
if (!context.newRegistryName) {
return localize('timeoutError', 'Timed out waiting for registry name to be generated. Please try another name.');
}
}
const resourceGroupAvailable: boolean = !!context.resourceGroup || await ResourceGroupListStep.isNameAvailable(context, name);
if (!resourceGroupAvailable) {
return `Resource group: ${resourceNameUnavailable}`;
}
if (context.resourceGroup) {
const managedEnvironmentAvailable: boolean = !!context.managedEnvironment || await ManagedEnvironmentNameStep.isNameAvailable(context, name, name);
if (!managedEnvironmentAvailable) {
return `Container apps environment: ${resourceNameUnavailable}`;
}
} else {
// Skip check - new resource group means unique managed environment
}
if (context.managedEnvironment) {
const containerAppAvailable: boolean = !!context.containerApp || await ContainerAppNameStep.isNameAvailable(context, name, name);
if (!containerAppAvailable) {
return `Container app: ${resourceNameUnavailable}`;
}
} else {
// Skip check - new managed environment means unique container app
}
return undefined;
});
}
private async getResourceNames(context: DeployWorkspaceProjectInternalContext, resourceBaseName: string): Promise<{
resourceGroupName: string;
logAnalyticsWorkspaceName: string;
managedEnvironmentName: string;
containerAppName: string;
}> {
const candidateResourceGroupName: string = resourceBaseName + resourceGroupSuffix;
const candidateEnvironmentName: string = resourceBaseName + managedEnvironmentSuffix;
const candidateLogAnalyticsName: string = resourceBaseName + logAnalyticsSuffix;
const candidateContainerAppName: string = resourceBaseName + containerAppSuffix;
const isCandidateResourceGroupAvailable: boolean = !ContainerAppNameStep.validateInput(candidateResourceGroupName) && await ResourceGroupListStep.isNameAvailable(context, candidateResourceGroupName);
const resourceGroupName: string = isCandidateResourceGroupAvailable ? candidateResourceGroupName : resourceBaseName;
const isCandidateLogAnalyticsNameAvailable: boolean = !ManagedEnvironmentNameStep.validateInput(candidateLogAnalyticsName) && await ManagedEnvironmentNameStep.isNameAvailable(context, resourceGroupName, candidateLogAnalyticsName);
const isCandidateEnvironmentNameAvailable: boolean = !ManagedEnvironmentNameStep.validateInput(candidateEnvironmentName) && await ManagedEnvironmentNameStep.isNameAvailable(context, resourceGroupName, candidateEnvironmentName);
const isCandidateContainerAppNameAvailable: boolean = !ContainerAppNameStep.validateInput(candidateContainerAppName) && await ContainerAppNameStep.isNameAvailable(context, resourceGroupName, candidateContainerAppName);
return {
resourceGroupName,
logAnalyticsWorkspaceName: isCandidateLogAnalyticsNameAvailable ? candidateLogAnalyticsName : resourceBaseName,
managedEnvironmentName: isCandidateEnvironmentNameAvailable ? candidateEnvironmentName : resourceBaseName,
containerAppName: isCandidateContainerAppNameAvailable ? candidateContainerAppName : resourceBaseName,
};
}
}

Просмотреть файл

@ -12,7 +12,6 @@ import { createActivityChildContext } from "../../../utils/activity/activityUtil
import { localize } from "../../../utils/localize";
import { type DeploymentConfigurationSettings } from "../settings/DeployWorkspaceProjectSettingsV2";
import { dwpSettingUtilsV2 } from "../settings/dwpSettingUtilsV2";
import { containerAppSuffix } from "./DefaultResourcesNameStep";
import { type DeployWorkspaceProjectInternalContext } from "./DeployWorkspaceProjectInternalContext";
const saveSettingsLabel: string = localize('saveSettingsLabel', 'Save deployment settings to workspace "{0}"', relativeSettingsFilePath);
@ -29,7 +28,7 @@ export class DeployWorkspaceProjectSaveSettingsStep extends ExecuteActivityOutpu
const configurationLabel: string | undefined = context.configurationIdx !== undefined ? deploymentConfigurations?.[context.configurationIdx].label : undefined;
const deploymentConfiguration: DeploymentConfigurationSettings = {
label: configurationLabel || removeCaSuffixIfExists(nonNullValueAndProp(context.containerApp, 'name')),
label: configurationLabel || nonNullValueAndProp(context.containerApp, 'name'),
type: 'AcrDockerBuildRequest',
dockerfilePath: path.relative(rootFolder.uri.fsPath, nonNullProp(context, 'dockerfilePath')),
srcPath: path.relative(rootFolder.uri.fsPath, context.srcPath || rootFolder.uri.fsPath) || ".",
@ -78,7 +77,3 @@ export class DeployWorkspaceProjectSaveSettingsStep extends ExecuteActivityOutpu
};
}
}
function removeCaSuffixIfExists(caName: string): string {
return caName.endsWith(containerAppSuffix) ? caName.slice(0, -containerAppSuffix.length) : caName;
}

Просмотреть файл

@ -0,0 +1,103 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ResourceGroupListStep } from "@microsoft/vscode-azext-azureutils";
import { AzureWizardPromptStep, nonNullValueAndProp } from "@microsoft/vscode-azext-utils";
import { ProgressLocation, window } from "vscode";
import { ext } from "../../../extensionVariables";
import { localize } from "../../../utils/localize";
import { ManagedEnvironmentNameStep } from "../../createManagedEnvironment/ManagedEnvironmentNameStep";
import { RegistryNameStep } from "../../image/imageSource/containerRegistry/acr/createAcr/RegistryNameStep";
import { type DeployWorkspaceProjectInternalContext } from "./DeployWorkspaceProjectInternalContext";
/** Names any app environment shared resources: `resource group`, `managed environment`, `container registry` */
export class SharedResourcesNameStep extends AzureWizardPromptStep<DeployWorkspaceProjectInternalContext> {
public async configureBeforePrompt(context: DeployWorkspaceProjectInternalContext): Promise<void> {
if ((context.resourceGroup || context.managedEnvironment) && !context.registry) {
// Generate a new name automatically without prompting
context.newRegistryName = await RegistryNameStep.tryGenerateRelatedName(context, context.managedEnvironment?.name || nonNullValueAndProp(context.resourceGroup, 'name'));
if (!context.newRegistryName) {
// Extremely unlikely that this code block would ever trigger
throw new Error(localize('failedToGenerateName', 'Failed to generate an available container registry name.'));
}
}
}
public async prompt(context: DeployWorkspaceProjectInternalContext): Promise<void> {
const resourceName: string = (await context.ui.showInputBox({
prompt: localize('sharedNamePrompt', 'Enter a name for the container app environment'),
validateInput: (name: string) => this.validateInput(context, name),
asyncValidationTask: (name: string) => this.validateNameAvailability(context, name)
})).trim();
ext.outputChannel.appendLog(localize('usingSharedName', 'User provided the name "{0}" for the container app environment.', resourceName));
!context.resourceGroup && (context.newResourceGroupName = resourceName);
!context.managedEnvironment && (context.newManagedEnvironmentName = resourceName);
}
public shouldPrompt(context: DeployWorkspaceProjectInternalContext): boolean {
return (!context.resourceGroup && !context.newResourceGroupName) ||
(!context.managedEnvironment && !context.newManagedEnvironmentName);
}
private validateInput(context: DeployWorkspaceProjectInternalContext, name: string = ''): string | undefined {
name = name.trim();
if (!context.managedEnvironment && !context.newManagedEnvironmentName) {
const result = ManagedEnvironmentNameStep.validateInput(name);
if (result) {
return result;
}
}
if (!context.registry) { // Skip checking newRegistryName since it gets set every time validateNameAvailability is run
// No symbols are allowed for ACR names
const nameWithoutSymbols: string = name.replace(/[^a-z0-9]+/g, '');
const result = RegistryNameStep.validateInput(nameWithoutSymbols);
if (result) {
return result;
}
}
return undefined;
}
protected async validateNameAvailability(context: DeployWorkspaceProjectInternalContext, name: string): Promise<string | undefined> {
return await window.withProgress({
location: ProgressLocation.Notification,
cancellable: false,
title: localize('verifyingAvailabilityTitle', 'Verifying resource name availability...')
}, async () => {
const resourceNameUnavailable: string = localize('resourceNameUnavailable', 'Resource name "{0}" is unavailable.', name);
if (context.registry) {
// Skip check, one already exists so don't need to worry about naming
} else {
context.newRegistryName = await RegistryNameStep.tryGenerateRelatedName(context, name);
if (!context.newRegistryName) {
return localize('timeoutError', 'Timed out waiting for registry name to be generated. Please try another name.');
}
}
const resourceGroupAvailable: boolean = !!context.resourceGroup || await ResourceGroupListStep.isNameAvailable(context, name);
if (!resourceGroupAvailable) {
return `Resource group: ${resourceNameUnavailable}`;
}
if (context.resourceGroup) {
const managedEnvironmentAvailable: boolean = !!context.managedEnvironment || await ManagedEnvironmentNameStep.isNameAvailable(context, name, name);
if (!managedEnvironmentAvailable) {
return `Container apps environment: ${resourceNameUnavailable}`;
}
} else {
// Skip check - new resource group means unique managed environment
}
return undefined;
});
}
}

Просмотреть файл

@ -18,10 +18,11 @@ import { ContainerAppUpdateStep } from "../../image/imageSource/ContainerAppUpda
import { ImageSourceListStep } from "../../image/imageSource/ImageSourceListStep";
import { IngressPromptStep } from "../../ingress/IngressPromptStep";
import { formatSectionHeader } from "../formatSectionHeader";
import { DefaultResourcesNameStep } from "./DefaultResourcesNameStep";
import { AppResourcesNameStep } from "./AppResourcesNameStep";
import { DeployWorkspaceProjectConfirmStep } from "./DeployWorkspaceProjectConfirmStep";
import { type DeployWorkspaceProjectInternalContext } from "./DeployWorkspaceProjectInternalContext";
import { DeployWorkspaceProjectSaveSettingsStep } from "./DeployWorkspaceProjectSaveSettingsStep";
import { SharedResourcesNameStep } from "./SharedResourcesNameStep";
import { ShouldSaveDeploySettingsPromptStep } from "./ShouldSaveDeploySettingsPromptStep";
import { getStartingConfiguration } from "./startingConfiguration/getStartingConfiguration";
@ -85,7 +86,8 @@ export async function deployWorkspaceProjectInternal(
const promptSteps: AzureWizardPromptStep<DeployWorkspaceProjectInternalContext>[] = [
new DeployWorkspaceProjectConfirmStep(!!options.suppressConfirmation),
new DefaultResourcesNameStep()
new SharedResourcesNameStep(),
new AppResourcesNameStep()
];
const executeSteps: AzureWizardExecuteStep<DeployWorkspaceProjectInternalContext>[] = [