diff --git a/README.md b/README.md index 837f5eefa..82f98fdd7 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,10 @@ Install the prerequisites for your desired language: * Stream logs from your Azure Function Apps * View and manage deployment slots > **NOTE**: To enable, set `azureFunctions.enableSlots` to true. -* Debug Java function project in Azure (experimental) +* Debug Node.js function project in Azure (experimental) > **NOTE**: To enable, set `azureFunctions.enableRemoteDebugging` to true. +* Debug Java function project in Azure (experimental) + > **NOTE**: To enable, set `azureFunctions.enableJavaRemoteDebugging` to true. ### Create New Project diff --git a/package.json b/package.json index f1f8ca33f..15c237926 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,8 @@ "onCommand:azureFunctions.deleteFunction", "onCommand:azureFunctions.deploy", "onCommand:azureFunctions.configureDeploymentSource", - "onCommand:azureFunctions.debugFunctionAppOnAzure", + "onCommand:azureFunctions.startRemoteDebug", + "onCommand:azureFunctions.startJavaRemoteDebug", "onCommand:azureFunctions.appSettings.add", "onCommand:azureFunctions.appSettings.download", "onCommand:azureFunctions.appSettings.upload", @@ -176,8 +177,13 @@ "category": "Azure Functions" }, { - "command": "azureFunctions.debugFunctionAppOnAzure", - "title": "%azFunc.debugFunctionAppOnAzure%", + "command": "azureFunctions.startRemoteDebug", + "title": "%azFunc.startRemoteDebug%", + "category": "Azure Functions" + }, + { + "command": "azureFunctions.startJavaRemoteDebug", + "title": "%azFunc.startJavaRemoteDebug%", "category": "Azure Functions" }, { @@ -452,10 +458,15 @@ "group": "4@2" }, { - "command": "azureFunctions.debugFunctionAppOnAzure", + "command": "azureFunctions.startRemoteDebug", "when": "view == azFuncTree && viewItem =~ /^azFunc(Production|)Slot$/ && config.azureFunctions.enableRemoteDebugging == true", "group": "5@1" }, + { + "command": "azureFunctions.startJavaRemoteDebug", + "when": "view == azFuncTree && viewItem =~ /^azFunc(Production|)Slot$/ && config.azureFunctions.enableJavaRemoteDebugging == true", + "group": "5@2" + }, { "command": "azureFunctions.viewProperties", "when": "view == azFuncTree && viewItem =~ /^azFunc(Production|)Slot$/", @@ -646,9 +657,13 @@ "when": "never" }, { - "command": "azureFunctions.debugFunctionAppOnAzure", + "command": "azureFunctions.startRemoteDebug", "when": "config.azureFunctions.enableRemoteDebugging == true" }, + { + "command": "azureFunctions.startJavaRemoteDebug", + "when": "config.azureFunctions.enableJavaRemoteDebugging == true" + }, { "command": "azureFunctions.createSlot", "when": "config.azureFunctions.enableSlots == true" @@ -831,6 +846,11 @@ "description": "%azFunc.enableRemoteDebugging%", "default": false }, + "azureFunctions.enableJavaRemoteDebugging": { + "type": "boolean", + "description": "%azFunc.enableJavaRemoteDebugging%", + "default": false + }, "azureFunctions.showProjectWarning": { "type": "boolean", "description": "%azFunc.showProjectWarningDescription%", diff --git a/package.nls.json b/package.nls.json index 7941886a5..8ce066b12 100644 --- a/package.nls.json +++ b/package.nls.json @@ -22,7 +22,8 @@ "azFunc.projectLanguageDescription": "The default language to use when performing operations in the Azure Functions extension (e.g. \"Create New Function\").", "azFunc.deploy": "Deploy to Function App...", "azFunc.configureDeploymentSource": "Configure Deployment Source...", - "azFunc.debugFunctionAppOnAzure": "Attach Debugger", + "azFunc.startRemoteDebug": "Start Remote Debugging", + "azFunc.startJavaRemoteDebug": "Attach Debugger", "azFunc.appSettings.add": "Add New Setting...", "azFunc.appSettings.download": "Download Remote Settings...", "azFunc.appSettings.upload": "Upload Local Settings...", @@ -46,7 +47,8 @@ "azFunc.showDeploySubpathWarningDescription": "Show a warning when the \"deploySubpath\" setting does not match the selected folder for deploying.", "azFunc.startStreamingLogs": "Start Streaming Logs", "azFunc.stopStreamingLogs": "Stop Streaming Logs", - "azFunc.enableRemoteDebugging": "Enable remote debugging, an experimental feature that only supports Java-based Functions Apps.", + "azFunc.enableRemoteDebugging": "Enable remote debugging for Node.js Function Apps running on Linux App Service plans. Consumption plans are not supported. (experimental)", + "azFunc.enableJavaRemoteDebugging": "Enable remote debugging for Java Functions Apps running on Windows. (experimental)", "azFunc.deleteProxy": "Delete Proxy...", "azFunc.pickProcessTimeoutDescription": "The timeout (in seconds) to be used when searching for the Azure Functions host process. Since a build is required every time you F5, you may need to adjust this based on how long your build takes.", "azFunc.templateVersion": "A runtime release version (any runtime) that species which templates will be used rather than the latest templates. This version will be used for ALL runtimes. (Requires a restart of VS Code to take effect)", diff --git a/src/commands/remoteDebug/checkForRemoteDebugSupport.ts b/src/commands/remoteDebug/checkForRemoteDebugSupport.ts new file mode 100644 index 000000000..cb6fc2b16 --- /dev/null +++ b/src/commands/remoteDebug/checkForRemoteDebugSupport.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { WebSiteManagementModels } from 'azure-arm-website'; + +export function checkForRemoteDebugSupport(siteConfig: WebSiteManagementModels.SiteConfig): void { + // Read siteConfig.linuxFxVersion to determine debugging support + // If the Function App is running on Windows, it will be empty + // If the Function App is running on Linux consumption, it will be empty + // If the Function App is running on a Linux App Service plan, it will contain Docker registry information, e.g. "DOCKER|repo.azurecr.io/image:tag" + // The blessed Node.js Docker image will be something like "DOCKER|mcr.microsoft.com/azure-functions/node:2.0-node8-appservice" + if (siteConfig.linuxFxVersion && siteConfig.linuxFxVersion.startsWith('DOCKER|mcr.microsoft.com/azure-functions/node')) { + return; + } + + throw new Error('Azure Remote Debugging is currently only supported for Node.js Function Apps running on Linux App Service plans. Consumption plans are not supported.'); +} diff --git a/src/commands/remoteDebug/startRemoteDebug.ts b/src/commands/remoteDebug/startRemoteDebug.ts new file mode 100644 index 000000000..bcdf25f0a --- /dev/null +++ b/src/commands/remoteDebug/startRemoteDebug.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { WebSiteManagementModels } from 'azure-arm-website'; +import * as vscode from 'vscode'; +import * as appservice from 'vscode-azureappservice'; +import { IActionContext } from 'vscode-azureextensionui'; +import { ext } from '../../extensionVariables'; +import { ProductionSlotTreeItem } from '../../tree/ProductionSlotTreeItem'; +import { SlotTreeItemBase } from '../../tree/SlotTreeItemBase'; +import { checkForRemoteDebugSupport } from './checkForRemoteDebugSupport'; + +export async function startRemoteDebug(context: IActionContext, node?: SlotTreeItemBase): Promise { + if (!node) { + node = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); + } + + const siteClient: appservice.SiteClient = node.root.client; + const siteConfig: WebSiteManagementModels.SiteConfig = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification }, async progress => { + appservice.reportMessage('Fetching site configuration...', progress); + return await siteClient.getSiteConfig(); + }); + + checkForRemoteDebugSupport(siteConfig); + + await appservice.startRemoteDebug(siteClient, siteConfig); +} diff --git a/src/DebugProxy.ts b/src/commands/remoteDebugJava/DebugProxy.ts similarity index 100% rename from src/DebugProxy.ts rename to src/commands/remoteDebugJava/DebugProxy.ts diff --git a/src/commands/remoteDebugFunctionApp.ts b/src/commands/remoteDebugJava/remoteDebugJavaFunctionApp.ts similarity index 93% rename from src/commands/remoteDebugFunctionApp.ts rename to src/commands/remoteDebugJava/remoteDebugJavaFunctionApp.ts index 136eaa37a..5c5d752ba 100644 --- a/src/commands/remoteDebugFunctionApp.ts +++ b/src/commands/remoteDebugJava/remoteDebugJavaFunctionApp.ts @@ -8,17 +8,17 @@ import * as portfinder from 'portfinder'; import * as vscode from 'vscode'; import { SiteClient } from 'vscode-azureappservice'; import { DialogResponses, IActionContext } from 'vscode-azureextensionui'; -import { DebugProxy } from '../DebugProxy'; -import { ext } from '../extensionVariables'; -import { localize } from '../localize'; -import { ProductionSlotTreeItem } from '../tree/ProductionSlotTreeItem'; -import { SlotTreeItemBase } from '../tree/SlotTreeItemBase'; -import { openUrl } from '../utils/openUrl'; +import { ext } from '../../extensionVariables'; +import { localize } from '../../localize'; +import { ProductionSlotTreeItem } from '../../tree/ProductionSlotTreeItem'; +import { SlotTreeItemBase } from '../../tree/SlotTreeItemBase'; +import { openUrl } from '../../utils/openUrl'; +import { DebugProxy } from './DebugProxy'; const HTTP_PLATFORM_DEBUG_PORT: string = '8898'; const JAVA_OPTS: string = `-Djava.net.preferIPv4Stack=true -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=127.0.0.1:${HTTP_PLATFORM_DEBUG_PORT}`; -export async function remoteDebugFunctionApp(context: IActionContext, node?: SlotTreeItemBase): Promise { +export async function remoteDebugJavaFunctionApp(context: IActionContext, node?: SlotTreeItemBase): Promise { if (!node) { node = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); } diff --git a/src/extension.ts b/src/extension.ts index 86a0bb8c1..ec984b127 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -39,7 +39,8 @@ import { startStreamingLogs } from './commands/logstream/startStreamingLogs'; import { stopStreamingLogs } from './commands/logstream/stopStreamingLogs'; import { openInPortal } from './commands/openInPortal'; import { pickFuncProcess } from './commands/pickFuncProcess'; -import { remoteDebugFunctionApp } from './commands/remoteDebugFunctionApp'; +import { startRemoteDebug } from './commands/remoteDebug/startRemoteDebug'; +import { remoteDebugJavaFunctionApp } from './commands/remoteDebugJava/remoteDebugJavaFunctionApp'; import { renameAppSetting } from './commands/renameAppSetting'; import { restartFunctionApp } from './commands/restartFunctionApp'; import { startFunctionApp } from './commands/startFunctionApp'; @@ -128,7 +129,8 @@ export async function activateInternal(context: vscode.ExtensionContext, perfSta registerCommand('azureFunctions.appSettings.encrypt', encryptLocalSettings); registerCommand('azureFunctions.appSettings.delete', async (actionContext: IActionContext, node?: AzExtTreeItem) => await deleteNode(actionContext, AppSettingTreeItem.contextValue, node)); registerCommand('azureFunctions.appSettings.toggleSlotSetting', toggleSlotSetting); - registerCommand('azureFunctions.debugFunctionAppOnAzure', remoteDebugFunctionApp); + registerCommand('azureFunctions.startRemoteDebug', startRemoteDebug); + registerCommand('azureFunctions.startJavaRemoteDebug', remoteDebugJavaFunctionApp); registerCommand('azureFunctions.deleteProxy', async (actionContext: IActionContext, node?: AzExtTreeItem) => await deleteNode(actionContext, ProxyTreeItem.contextValue, node)); registerCommand('azureFunctions.installOrUpdateFuncCoreTools', installOrUpdateFuncCoreTools); registerCommand('azureFunctions.uninstallFuncCoreTools', uninstallFuncCoreTools);