Skip to content

Commit 6227d90

Browse files
fix: Support multiple internal extensions (#74)
* fix: Support multiple internal extensions * chore: Add comment
1 parent 879ba67 commit 6227d90

File tree

4 files changed

+150
-21
lines changed

4 files changed

+150
-21
lines changed

.vscode/launch.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@
2424
"type": "node",
2525
"cwd": "${workspaceRoot}/test/cdk-basic"
2626
},
27+
{
28+
"name": "LLDebugger - CDK basic - remove",
29+
"program": "${workspaceRoot}/node_modules/tsx/dist/cli.mjs",
30+
"args": ["../../src/lldebugger.ts", "-c environment=test", "-r"],
31+
"request": "launch",
32+
"skipFiles": ["<node_internals>/**"],
33+
"console": "integratedTerminal",
34+
"type": "node",
35+
"cwd": "${workspaceRoot}/test/cdk-basic"
36+
},
2737
{
2838
"name": "LLDebugger - CDK basic - monorepo",
2939
"program": "${workspaceRoot}/node_modules/tsx/dist/cli.mjs",

src/extension/interceptor.ts

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const workerId = crypto.randomBytes(16).toString('hex');
66
const topic = `${process.env.LLD_DEBUGGER_ID}/events/${workerId}`;
77

88
const ORIGINAL_HANDLER_KEY = 'ORIGINAL_HANDLER';
9+
const originalHandlerName = process.env[ORIGINAL_HANDLER_KEY];
910
const observableInterval = process.env.LLD_OBSERVABLE_INTERVAL
1011
? parseInt(process.env.LLD_OBSERVABLE_INTERVAL!)
1112
: 0;
@@ -106,10 +107,65 @@ async function regularMode(context: any, event: any) {
106107
* Observable mode, which sends the event to the IoT service and doesn't wait for a response. It executes the original handler.
107108
*/
108109
async function observableMode(context: any, event: any) {
109-
const regularHandler = async () => {
110-
const handler = await getOriginalHandler();
111-
return await handler(event, context);
112-
};
110+
let regularHandler: undefined | (() => Promise<any>) = undefined;
111+
112+
if (process.env.LLD_INITIAL_AWS_LAMBDA_EXEC_WRAPPER) {
113+
try {
114+
Logger.log(
115+
`Another extensions exists ${process.env.LLD_INITIAL_AWS_LAMBDA_EXEC_WRAPPER}.`,
116+
);
117+
118+
const { promisify } = require('util');
119+
const exec = require('child_process').exec;
120+
const execAsync = promisify(exec);
121+
122+
// read the content of the script
123+
const fs = require('fs/promises');
124+
const originalScript = await fs.readFile(
125+
process.env.LLD_INITIAL_AWS_LAMBDA_EXEC_WRAPPER,
126+
'utf8',
127+
);
128+
129+
Logger.verbose('Original script', originalScript);
130+
131+
// - set original handler
132+
// - run second extension script
133+
// - print environment variables
134+
const script = `export _HANDLER=${process.env.ORIGINAL_HANDLER}
135+
${originalScript}
136+
echo _HANDLER=$_HANDLER`;
137+
138+
Logger.verbose('Execute script', script);
139+
140+
const response = await execAsync(script);
141+
142+
Logger.verbose(`Output of the script: ${response.stdout}`);
143+
// parse environment variables I got from the script
144+
const handlerLine = response.stdout
145+
.split('\n')
146+
.find((line: string) => line.startsWith('_HANDLER'));
147+
const oldHandler = handlerLine.split('=')[1];
148+
149+
Logger.verbose(`Getting handler "${oldHandler}" for another extension`);
150+
151+
regularHandler = async () => {
152+
const handler = await getOriginalHandler(oldHandler);
153+
return await handler(event, context);
154+
};
155+
} catch (e: any) {
156+
Logger.error(
157+
`Error while running the initial AWS_LAMBDA_EXEC_WRAPPER: ${e.message}`,
158+
e,
159+
);
160+
}
161+
}
162+
163+
if (!regularHandler) {
164+
regularHandler = async () => {
165+
const handler = await getOriginalHandler(originalHandlerName);
166+
return await handler(event, context);
167+
};
168+
}
113169

114170
const observableHandler = async () => {
115171
// prevent sending too many events
@@ -155,14 +211,18 @@ async function observableMode(context: any, event: any) {
155211
return response;
156212
}
157213

158-
async function getOriginalHandler(): Promise<any> {
214+
async function getOriginalHandler(
215+
originalHandlerName: string | undefined,
216+
): Promise<any> {
217+
Logger.verbose('Original handler:', originalHandlerName);
218+
159219
// @ts-ignore
160220
const { load } = await import('./aws/UserFunction');
161221

162-
if (process.env[ORIGINAL_HANDLER_KEY] === undefined)
222+
if (originalHandlerName === undefined)
163223
throw Error('Missing original handler');
164224
return load(
165225
process.env.LAMBDA_TASK_ROOT!,
166-
process.env[ORIGINAL_HANDLER_KEY],
226+
originalHandlerName,
167227
) as Promise<any>;
168228
}

src/infraDeploy.ts

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ let iamClient: IAMClient | undefined;
2626

2727
const inlinePolicyName = 'LambdaLiveDebuggerPolicy';
2828
const layerName = 'LambdaLiveDebugger';
29+
const lldWrapperPath = '/opt/lld-wrapper';
2930

3031
/**
3132
* Policy document to attach to the Lambda role
@@ -300,7 +301,16 @@ async function removeLayerFromLambda(functionName: string) {
300301
);
301302
}
302303

303-
const ddlEnvironmentVariables = getEnvironmentVarablesForDebugger('xxx', 0);
304+
const initalExecWraper =
305+
environmentVariables.LLD_INITIAL_AWS_LAMBDA_EXEC_WRAPPER;
306+
307+
const ddlEnvironmentVariables = getEnvironmentVarablesForDebugger({
308+
// set dummy data, so we just get the list of environment variables
309+
functionId: 'xxx',
310+
timeout: 0,
311+
verbose: true,
312+
initalExecWraper: 'test',
313+
});
304314

305315
// check if environment variables are set for each property
306316
for (const [key] of Object.entries(ddlEnvironmentVariables)) {
@@ -323,10 +333,22 @@ async function removeLayerFromLambda(functionName: string) {
323333
//remove environment variables
324334
for (const [key] of Object.entries(ddlEnvironmentVariables)) {
325335
if (environmentVariables && environmentVariables[key]) {
326-
delete environmentVariables[key];
336+
if (key === 'AWS_LAMBDA_EXEC_WRAPPER') {
337+
if (environmentVariables[key] === lldWrapperPath) {
338+
delete environmentVariables[key];
339+
} else {
340+
// do not remove the original AWS_LAMBDA_EXEC_WRAPPER that was set before LLD
341+
}
342+
} else {
343+
delete environmentVariables[key];
344+
}
327345
}
328346
}
329347

348+
if (initalExecWraper) {
349+
environmentVariables.AWS_LAMBDA_EXEC_WRAPPER = initalExecWraper;
350+
}
351+
330352
Logger.verbose(
331353
'New environment variables',
332354
JSON.stringify(environmentVariables, null, 2),
@@ -445,10 +467,24 @@ async function attachLayerToLambda(
445467
Logger.verbose('Layer with the wrong version attached to the function');
446468
}
447469

448-
const ddlEnvironmentVariables = getEnvironmentVarablesForDebugger(
470+
// support for multiple internal Lambda extensions
471+
const initalExecWraper =
472+
environmentVariables.AWS_LAMBDA_EXEC_WRAPPER !== lldWrapperPath
473+
? environmentVariables.AWS_LAMBDA_EXEC_WRAPPER
474+
: undefined;
475+
476+
if (initalExecWraper) {
477+
Logger.warn(
478+
`[Function ${functionName}] Another internal Lambda extension is already attached to the function, which might cause unpredictable behavior.`,
479+
);
480+
}
481+
482+
const ddlEnvironmentVariables = getEnvironmentVarablesForDebugger({
449483
functionId,
450-
initialTimeout,
451-
);
484+
timeout: initialTimeout,
485+
verbose: Configuration.config.verbose,
486+
initalExecWraper,
487+
});
452488

453489
// check if environment variables are already set for each property
454490
for (const [key, value] of Object.entries(ddlEnvironmentVariables)) {
@@ -551,22 +587,36 @@ async function addPolicyToLambdaRole(functionName: string) {
551587

552588
/**
553589
* Get the environment variables for the Lambda function
554-
* @param functionId
555-
* @param timeout
556-
* @returns
557590
*/
558-
function getEnvironmentVarablesForDebugger(
559-
functionId: string,
560-
timeout: number | undefined,
561-
): Record<string, string> {
562-
return {
591+
function getEnvironmentVarablesForDebugger({
592+
functionId,
593+
timeout,
594+
verbose,
595+
initalExecWraper,
596+
}: {
597+
functionId: string;
598+
timeout: number | undefined;
599+
verbose: boolean | undefined;
600+
initalExecWraper: string | undefined;
601+
}): Record<string, string> {
602+
const env: Record<string, string> = {
563603
LLD_FUNCTION_ID: functionId,
564-
AWS_LAMBDA_EXEC_WRAPPER: '/opt/lld-wrapper',
604+
AWS_LAMBDA_EXEC_WRAPPER: lldWrapperPath,
565605
LLD_DEBUGGER_ID: Configuration.config.debuggerId,
566606
LLD_INITIAL_TIMEOUT: timeout ? timeout.toString() : '-1', // should never be negative
567607
LLD_OBSERVABLE_MODE: Configuration.config.observable ? 'true' : 'false',
568608
LLD_OBSERVABLE_INTERVAL: Configuration.config.interval.toString(),
569609
};
610+
611+
if (initalExecWraper) {
612+
env.LLD_INITIAL_AWS_LAMBDA_EXEC_WRAPPER = initalExecWraper;
613+
}
614+
615+
if (verbose) {
616+
env.LLD_VERBOSE = 'true';
617+
}
618+
619+
return env;
570620
}
571621

572622
/**

src/logger.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,20 @@ function setVerbose(enabled: boolean) {
6363
verboseEnabled = enabled;
6464
}
6565

66+
/**
67+
* Check if verbose logging is enabled
68+
* @returns Whether verbose logging is enabled
69+
*/
70+
function isVerbose() {
71+
return verboseEnabled;
72+
}
73+
6674
export const Logger = {
6775
log,
6876
error,
6977
warn,
7078
important,
7179
verbose,
7280
setVerbose,
81+
isVerbose,
7382
};

0 commit comments

Comments
 (0)