Separate name steps for shared resources and app resources (#668)
This commit is contained in:
Родитель
473564550c
Коммит
3093986dad
|
@ -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>[] = [
|
||||
|
|
Загрузка…
Ссылка в новой задаче