Skip to content

Commit 8cd876d

Browse files
authored
feat(lambda): Add Remote debugging support to Lambda Remote Invoke (#7678)
## Problem * Enhanced Developer Experience - Current Remote Lambda debugging options are limited, primarily relying on print statements. LDK will significantly improve the developer experience by allowing real-time inspection of variables and execution flow. * Authentic Environment Debugging - Customers are eager to debug in the actual Lambda environment. However, Lambda doesn't allow direct SSH connections from external sources, making it challenging to access the runtime for debugging purposes. * Advantages over Local Debugging - While local debugging is a common alternative, Remote debugging offers significant benefits: Access to resources within VPCs, Ability to follow specific IAM rules and permissions which are typically not possible in a local debugging solution. * Efficient Problem Resolution - Debugging in the real Lambda environment provides the shortest path to identifying and resolving issues, as it eliminates [discrepancies](aws/aws-lambda-base-images#112) between local and real Lambda environments. ## Solution Remote debugging (LDK) enable developers to remote debug live Lambda functions from AWS Toolkit for Visual Studio Code. LDK creates a secure tunnel between a developer’s local computer and a live Lambda function running in the cloud. With LDK, developers can use Visual Studio Code debugger to debug a running Lambda function, set break points, inspect variables, and step through the code. ### File structure - ldkClient.ts - Implement API calls to IoT & Lambda - localproxy.ts - Implement the local proxy to connect to the IoT SecureTunneling(ST) websocket and create a proxy tcp server - ldkController.ts - Control the debug workflow ### UI update ![image](https://github.com/user-attachments/assets/e526a46d-952c-45c5-aca6-77353d46ca7a) ### Arch ![image](https://github.com/user-attachments/assets/aaf90b74-3060-44b7-bde6-383e15b04910) ### Sequence Diagram ![image](https://github.com/user-attachments/assets/866b61dc-3872-41b0-a140-7b35d0c2a8be) --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 2d7fc6f commit 8cd876d

35 files changed

+6234
-201
lines changed

package-lock.json

Lines changed: 8 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,8 +609,10 @@
609609
"whatwg-url": "^14.0.0",
610610
"winston": "^3.11.0",
611611
"winston-transport": "^4.6.0",
612+
"ws": "^8.16.0",
612613
"xml2js": "^0.6.1",
613614
"yaml-cfn": "^0.3.2",
615+
"protobufjs": "^7.2.6",
614616
"@svgdotjs/svg.js": "^3.0.16",
615617
"svgdom": "^0.1.0",
616618
"jaro-winkler": "^0.2.8"

packages/core/package.nls.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
"AWS.configuration.description.amazonq.workspaceIndexCacheDirPath": "The path to the directory that contains the cache of the index of your workspace files",
100100
"AWS.configuration.description.amazonq.ignoredSecurityIssues": "Specifies a list of code issue identifiers that Amazon Q should ignore when reviewing your workspace. Each item in the array should be a unique string identifier for a specific code issue. This allows you to suppress notifications for known issues that you've assessed and determined to be false positives or not applicable to your project. Use this setting with caution, as it may cause you to miss important security alerts.",
101101
"AWS.configuration.description.amazonq.proxy.certificateAuthority": "Path to a Certificate Authority (PEM file) for SSL/TLS verification when using a proxy.",
102-
"AWS.command.apig.invokeRemoteRestApi": "Invoke in the cloud",
102+
"AWS.command.apig.invokeRemoteRestApi": "Invoke remotely",
103103
"AWS.command.apig.invokeRemoteRestApi.cn": "Invoke on Amazon",
104104
"AWS.appBuilder.explorerTitle": "Application Builder",
105105
"AWS.appBuilder.explorerNode.noApps": "[This resource is not yet supported.]",
@@ -159,11 +159,12 @@
159159
"AWS.command.aboutToolkit": "About",
160160
"AWS.command.downloadLambda": "Download...",
161161
"AWS.command.uploadLambda": "Upload Lambda...",
162-
"AWS.command.invokeLambda": "Invoke in the cloud",
162+
"AWS.command.invokeLambda": "Invoke Remotely",
163163
"AWS.command.openLambdaFile": "Open your Lambda code",
164164
"AWS.command.quickDeployLambda": "Save and deploy your code",
165165
"AWS.command.openLambdaWorkspace": "Open in a workspace",
166166
"AWS.command.invokeLambda.cn": "Invoke on Amazon",
167+
"AWS.command.remoteDebugging.clearSnapshot": "Reset Lambda Remote Debugging Snapshot",
167168
"AWS.command.lambda.convertToSam": "Convert to SAM Application",
168169
"AWS.command.refreshAwsExplorer": "Refresh Explorer",
169170
"AWS.command.refreshCdkExplorer": "Refresh CDK Explorer",

packages/core/src/awsService/appBuilder/explorer/nodes/deployedNode.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
s3BucketType,
2828
} from '../../../../shared/cloudformation/cloudformation'
2929
import { ToolkitError } from '../../../../shared/errors'
30+
import { ResourceTreeEntity } from '../samProject'
3031

3132
const localize = nls.loadMessageBundle()
3233
export interface DeployedResource {
@@ -77,7 +78,8 @@ export async function generateDeployedNode(
7778
deployedResource: any,
7879
regionCode: string,
7980
stackName: string,
80-
resourceTreeEntity: any
81+
resourceTreeEntity: ResourceTreeEntity,
82+
location?: vscode.Uri
8183
): Promise<any[]> {
8284
let newDeployedResource: any
8385
const partitionId = globals.regionProvider.getPartitionId(regionCode) ?? defaultPartition
@@ -90,7 +92,13 @@ export async function generateDeployedNode(
9092
try {
9193
configuration = (await defaultClient.getFunction(deployedResource.PhysicalResourceId))
9294
.Configuration as Lambda.FunctionConfiguration
93-
newDeployedResource = new LambdaFunctionNode(lambdaNode, regionCode, configuration)
95+
newDeployedResource = new LambdaFunctionNode(
96+
lambdaNode,
97+
regionCode,
98+
configuration,
99+
undefined,
100+
location ? vscode.Uri.joinPath(location, resourceTreeEntity.CodeUri ?? '').fsPath : undefined
101+
)
94102
} catch (error: any) {
95103
getLogger().error('Error getting Lambda configuration: %O', error)
96104
throw ToolkitError.chain(error, 'Error getting Lambda configuration', {

packages/core/src/awsService/appBuilder/explorer/nodes/resourceNode.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ export class ResourceNode implements TreeNode {
6060
this.deployedResource,
6161
this.region,
6262
this.stackName,
63-
this.resourceTreeEntity
63+
this.resourceTreeEntity,
64+
this.location.projectRoot
6465
)
6566
}
6667
if (this.resourceTreeEntity.Type === SERVERLESS_FUNCTION_TYPE) {

packages/core/src/awsService/appBuilder/utils.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -569,19 +569,24 @@ export async function getLambdaHandlerFile(
569569
})
570570
}
571571

572+
// if this function is used to get handler from a just downloaded lambda function zip. codeUri will be ''
573+
if (codeUri !== '') {
574+
folderUri = vscode.Uri.joinPath(folderUri, codeUri)
575+
}
576+
572577
const handlerParts = handler.split('.')
573578
// sample: app.lambda_handler -> app.rb
574579
if (family === RuntimeFamily.Ruby) {
575580
// Ruby supports namespace/class handlers as well, but the path is
576581
// guaranteed to be slash-delimited so we can assume the first part is
577582
// the path
578-
return vscode.Uri.joinPath(folderUri, codeUri, handlerParts.slice(0, handlerParts.length - 1).join('/') + '.rb')
583+
return vscode.Uri.joinPath(folderUri, handlerParts.slice(0, handlerParts.length - 1).join('/') + '.rb')
579584
}
580585

581586
// sample:app.lambda_handler -> app.py
582587
if (family === RuntimeFamily.Python) {
583588
// Otherwise (currently Node.js and Python) handle dot-delimited paths
584-
return vscode.Uri.joinPath(folderUri, codeUri, handlerParts.slice(0, handlerParts.length - 1).join('/') + '.py')
589+
return vscode.Uri.joinPath(folderUri, handlerParts.slice(0, handlerParts.length - 1).join('/') + '.py')
585590
}
586591

587592
// sample: app.handler -> app.mjs/app.js
@@ -591,23 +596,23 @@ export async function getLambdaHandlerFile(
591596
const handlerPath = path.dirname(handlerName)
592597
const handlerFile = path.basename(handlerName)
593598
const pattern = new vscode.RelativePattern(
594-
vscode.Uri.joinPath(folderUri, codeUri, handlerPath),
595-
`${handlerFile}.{js,mjs}`
599+
vscode.Uri.joinPath(folderUri, handlerPath),
600+
`${handlerFile}.{js,mjs,cjs,ts}`
596601
)
597602
return searchHandlerFile(folderUri, pattern)
598603
}
599604
// search directly under Code uri for Dotnet and java
600605
// sample: ImageResize::ImageResize.Function::FunctionHandler -> Function.cs
601606
if (family === RuntimeFamily.DotNet) {
602607
const handlerName = path.basename(handler.split('::')[1].replaceAll('.', '/'))
603-
const pattern = new vscode.RelativePattern(vscode.Uri.joinPath(folderUri, codeUri), `${handlerName}.cs`)
608+
const pattern = new vscode.RelativePattern(folderUri, `${handlerName}.cs`)
604609
return searchHandlerFile(folderUri, pattern)
605610
}
606611

607612
// sample: resizer.App::handleRequest -> App.java
608613
if (family === RuntimeFamily.Java) {
609614
const handlerName = handler.split('::')[0].replaceAll('.', '/')
610-
const pattern = new vscode.RelativePattern(vscode.Uri.joinPath(folderUri, codeUri), `**/${handlerName}.java`)
615+
const pattern = new vscode.RelativePattern(folderUri, `**/${handlerName}.java`)
611616
return searchHandlerFile(folderUri, pattern)
612617
}
613618
}

packages/core/src/lambda/activation.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { uploadLambdaCommand } from './commands/uploadLambda'
1313
import { LambdaFunctionNode } from './explorer/lambdaFunctionNode'
1414
import { downloadLambdaCommand, openLambdaFile } from './commands/downloadLambda'
1515
import { tryRemoveFolder } from '../shared/filesystemUtilities'
16-
import { ExtContext } from '../shared/extensions'
1716
import { invokeRemoteLambda } from './vue/remoteInvoke/invokeLambda'
1817
import { registerSamDebugInvokeVueCommand, registerSamInvokeVueCommand } from './vue/configEditor/samInvokeBackend'
1918
import { Commands } from '../shared/vscode/commands2'
@@ -42,6 +41,8 @@ import { registerLambdaUriHandler } from './uriHandlers'
4241
import globals from '../shared/extensionGlobals'
4342

4443
const localize = nls.loadMessageBundle()
44+
import { activateRemoteDebugging } from './remoteDebugging/ldkController'
45+
import { ExtContext } from '../shared/extensions'
4546

4647
async function openReadme() {
4748
const readmeUri = vscode.Uri.file(await getReadme())
@@ -263,4 +264,6 @@ export async function activate(context: ExtContext): Promise<void> {
263264

264265
registerLambdaUriHandler()
265266
)
267+
268+
void activateRemoteDebugging()
266269
}

0 commit comments

Comments
 (0)