diff --git a/package.json b/package.json index bd03c1d29..b474daf37 100644 --- a/package.json +++ b/package.json @@ -1088,6 +1088,11 @@ "type": "boolean", "description": "%azureFunctions.enableFlexConsumption%", "default": false + }, + "azureFunctions.showFlexEventGridWarning": { + "type": "boolean", + "description": "%azureFunctions.showFlexEventGridWarning%", + "default": true } } } diff --git a/package.nls.json b/package.nls.json index 3ef9793e4..0e5009158 100644 --- a/package.nls.json +++ b/package.nls.json @@ -80,6 +80,7 @@ "azureFunctions.showDeprecatedStacks": "Show deprecated runtime stacks when creating a Function App in Azure. WARNING: These stacks may be removed at any time and may not be available in all regions.", "azureFunctions.showExplorer": "Show or hide the Azure Functions Explorer", "azureFunctions.showExtensionsCsprojWarning": "Show a warning when an Azure Functions project was detected that has mismatched \"extensions.csproj\" configuration.", + "%azureFunctions.showFlexEventGridWarning%": "Show a warning when deploying a Azure Blob Storage Trigger (using Event Grid) to a Flex Consumption Function App.", "azureFunctions.showHiddenStacks": "Show hidden runtime stacks when creating a Function App in Azure. WARNING: These stacks may be in preview or may not be available in all regions.", "azureFunctions.showMultiCoreToolsWarning": "Show a warning if multiple installs of Azure Functions Core Tools are detected.", "azureFunctions.showProjectWarning": "Show a warning when an Azure Functions project was detected that has not been initialized for use in VS Code.", diff --git a/src/commands/createFunctionApp/FunctionAppCreateStep.ts b/src/commands/createFunctionApp/FunctionAppCreateStep.ts index da308482f..15104637e 100644 --- a/src/commands/createFunctionApp/FunctionAppCreateStep.ts +++ b/src/commands/createFunctionApp/FunctionAppCreateStep.ts @@ -133,7 +133,7 @@ export class FunctionAppCreateStep extends AzureWizardExecuteStep im.isDefault)?.size ?? 2048, alwaysReady: [], triggers: null diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index 197bb38b5..84c363530 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -178,7 +178,7 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | } ); - await notifyDeployComplete(context, node, context.workspaceFolder); + await notifyDeployComplete(context, node, context.workspaceFolder, isFlexConsumption); } async function updateWorkerProcessTo64BitIfRequired(context: IDeployContext, siteConfig: SiteConfigResource, node: SlotTreeItem, language: ProjectLanguage, durableStorageType: DurableBackendValues | undefined): Promise { diff --git a/src/commands/deploy/notifyDeployComplete.ts b/src/commands/deploy/notifyDeployComplete.ts index 4426044ed..bd5b4e6f4 100644 --- a/src/commands/deploy/notifyDeployComplete.ts +++ b/src/commands/deploy/notifyDeployComplete.ts @@ -14,13 +14,23 @@ import { RemoteFunctionsTreeItem } from '../../tree/remoteProject/RemoteFunction import { nonNullValue } from '../../utils/nonNull'; import { uploadAppSettings } from '../appSettings/uploadAppSettings'; import { startStreamingLogs } from '../logstream/startStreamingLogs'; +import { hasRemoteEventGridBlobTrigger, promptForEventGrid } from './promptForEventGrid'; -export async function notifyDeployComplete(context: IActionContext, node: SlotTreeItem, workspaceFolder: WorkspaceFolder): Promise { +export async function notifyDeployComplete(context: IActionContext, node: SlotTreeItem, workspaceFolder: WorkspaceFolder, isFlexConsumption?: boolean): Promise { const deployComplete: string = localize('deployComplete', 'Deployment to "{0}" completed.', node.site.fullName); const viewOutput: MessageItem = { title: localize('viewOutput', 'View output') }; const streamLogs: MessageItem = { title: localize('streamLogs', 'Stream logs') }; const uploadSettings: MessageItem = { title: localize('uploadAppSettings', 'Upload settings') }; + try { + const shouldCheckEventSystemTopics = isFlexConsumption && await hasRemoteEventGridBlobTrigger(context, node); + if (shouldCheckEventSystemTopics) { + await promptForEventGrid(context, workspaceFolder); + } + } catch (err) { + // ignore this error, don't block deploy for this check + } + // Don't wait void window.showInformationMessage(deployComplete, streamLogs, uploadSettings, viewOutput).then(async result => { await callWithTelemetryAndErrorHandling('postDeploy', async (postDeployContext: IActionContext) => { diff --git a/src/commands/deploy/promptForEventGrid.ts b/src/commands/deploy/promptForEventGrid.ts new file mode 100644 index 000000000..5b02bacd9 --- /dev/null +++ b/src/commands/deploy/promptForEventGrid.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type FunctionEnvelope } from "@azure/arm-appservice"; +import { DialogResponses, type IActionContext, type IAzureMessageOptions } from "@microsoft/vscode-azext-utils"; +import * as retry from 'p-retry'; +import { type WorkspaceFolder } from "vscode"; +import { localize } from "../../localize"; +import { type IBindingTemplate } from "../../templates/IBindingTemplate"; +import { type SlotTreeItem } from "../../tree/SlotTreeItem"; +import { getWorkspaceSetting, updateWorkspaceSetting } from "../../vsCodeConfig/settings"; + +export async function hasRemoteEventGridBlobTrigger(context: IActionContext, node: SlotTreeItem): Promise { + const retries = 3; + const client = await node.site.createClient(context); + + const funcs = await retry( + async () => { + // Load more currently broken https://github.com/Azure/azure-sdk-for-js/issues/20380 + const response = await client.listFunctions(); + const failedToList = localize('failedToList', 'Failed to list functions.'); + + // https://github.com/Azure/azure-functions-host/issues/3502 + if (!Array.isArray(response)) { + throw new Error(failedToList); + } + + return response; + }, + { retries, minTimeout: 10 * 1000 } + ); + + return funcs.some(f => { + const bindings = (f.config as { bindings: IBindingTemplate[] }).bindings; + return bindings.some(b => b.type === 'blobTrigger'); + }); +} + +export async function promptForEventGrid(context: IActionContext, workspaceFolder: WorkspaceFolder): Promise { + const showFlexEventGridWarning = await getWorkspaceSetting('showFlexEventGridWarning'); + if (!showFlexEventGridWarning) { + return; + } + + const eventGridWarning = localize( + 'eventGridWarning', + `Usage of an Event Grid based blob trigger requires an Event Grid subscription created on an Azure Storage v2 account. If you haven't already, you need to create a Event Grid subscription to complete your deployment.`); + const options: IAzureMessageOptions = { learnMoreLink: 'https://aka.ms/learnMoreEventGridSubscription' }; + const result = await context.ui.showWarningMessage(eventGridWarning, options, { title: 'Close' }, DialogResponses.dontWarnAgain); + if (result === DialogResponses.dontWarnAgain) { + await updateWorkspaceSetting('showFlexEventGridWarning', false, workspaceFolder); + } +}