Skip to content

Commit ca20f2a

Browse files
committed
feat: Support Python lambdas
1 parent 8ec7330 commit ca20f2a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+13057
-16917
lines changed

.projen/tasks.json

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

.projenrc.js.bak

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ const project = new awscdk.AwsCdkConstructLibrary({
2525
singleQuote: true,
2626
},
2727
},
28+
jestOptions: {
29+
jestConfig: {
30+
moduleNameMapper: {
31+
['^aws-cdk-lib/.warnings.jsii.js$']: '<rootDir>/node_modules/aws-cdk-lib/.warnings.jsii.js',
32+
},
33+
},
34+
},
2835

2936
// deps: [], /* Runtime dependencies of this module. */
3037
// description: undefined, /* The description is just a string that helps people understand the purpose of the package. */

API.md

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

common/SpyEventSender.ts

Lines changed: 69 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
1-
import {
2-
ApiGatewayManagementApi,
3-
PostToConnectionCommand,
4-
} from '@aws-sdk/client-apigatewaymanagementapi';
5-
import {
6-
AttributeValue,
7-
DeleteItemCommand,
8-
DynamoDBClient,
9-
ScanCommand,
10-
} from '@aws-sdk/client-dynamodb';
11-
121
import { unmarshall } from '@aws-sdk/util-dynamodb';
2+
import iot from 'aws-iot-device-sdk';
133
import {
144
DynamoDBStreamEvent,
155
S3Event,
166
SNSEvent,
177
EventBridgeEvent,
188
SQSEvent,
199
} from 'aws-lambda';
10+
import { v4 } from 'uuid';
11+
import {
12+
fragment,
13+
getConnection,
14+
SSPY_TOPIC,
15+
} from '../listener/iot-connection';
2016
import { envVariableNames } from '../src/common/envVariableNames';
2117
import { DynamoDBSpyEvent } from './spyEvents/DynamoDBSpyEvent';
2218
import { EventBridgeRuleSpyEvent } from './spyEvents/EventBridgeRuleSpyEvent';
@@ -28,27 +24,32 @@ import { SpyMessage } from './spyEvents/SpyMessage';
2824
import { SqsSpyEvent } from './spyEvents/SqsSpyEvent';
2925

3026
export class SpyEventSender {
31-
ddb = new DynamoDBClient({
32-
region: process.env.AWS_REGION,
33-
});
3427
debugMode = process.env[envVariableNames.SSPY_DEBUG] === 'true';
35-
apigwManagementApi = new ApiGatewayManagementApi({
36-
apiVersion: '2018-11-29',
37-
endpoint: process.env[envVariableNames.SSPY_WS_ENDPOINT]!,
38-
});
39-
connections: Record<string, AttributeValue>[] | undefined;
40-
41-
constructor(params?: {
42-
log: (message: string, ...optionalParams: any[]) => void;
43-
logError: (message: string, ...optionalParams: any[]) => void;
28+
connection: iot.device | undefined;
29+
scope: string;
30+
31+
constructor(params: {
32+
log?: (message: string, ...optionalParams: any[]) => void;
33+
logError?: (message: string, ...optionalParams: any[]) => void;
34+
scope: string;
4435
}) {
45-
if (params?.log) {
36+
if (params.log) {
4637
this.log = params.log;
4738
}
4839

49-
if (params?.logError) {
40+
if (params.logError) {
5041
this.logError = params.logError;
5142
}
43+
44+
this.scope = params.scope;
45+
}
46+
47+
public async close() {
48+
this.connection?.end();
49+
}
50+
51+
public async connect() {
52+
this.connection = await getConnection(this.debugMode);
5253
}
5354

5455
public async publishSpyEvent(event: any) {
@@ -59,17 +60,6 @@ export class SpyEventSender {
5960
);
6061
this.log('ARN to names mapping', JSON.stringify(mapping));
6162

62-
let connectionData;
63-
64-
const scanParams = new ScanCommand({
65-
TableName: process.env[envVariableNames.SSPY_WS_TABLE_NAME] as string,
66-
ProjectionExpression: 'connectionId',
67-
});
68-
69-
connectionData = await this.ddb.send(scanParams);
70-
71-
this.connections = connectionData.Items;
72-
7363
const postDataPromises: Promise<any>[] = [];
7464

7565
if (event?.Records && event.Records[0]?.Sns) {
@@ -227,45 +217,55 @@ export class SpyEventSender {
227217
await Promise.all(postDataPromises);
228218
}
229219

230-
private async postData(spyMessage: Omit<SpyMessage, 'timestamp'>) {
231-
this.log('Post spy message', JSON.stringify(spyMessage));
220+
private encode(input: any): fragment[] {
221+
const payload = JSON.stringify(input);
222+
const parts = payload.match(/.{1,50000}/g);
223+
if (!parts) return [];
224+
this.log(`Encoded iot message, ${parts.length}`);
225+
const id = v4();
226+
return parts.map((part, index) => ({
227+
id,
228+
index,
229+
count: parts.length,
230+
data: part,
231+
}));
232+
}
232233

233-
if (!this.connections) {
234-
return;
234+
private async postData(spyMessage: Omit<SpyMessage, 'timestamp'>) {
235+
if (this.connection === undefined) {
236+
throw new Error(
237+
'No IoT connection created yet, did you forget to call connect()?'
238+
);
235239
}
236240

237-
const postCalls = this.connections.map(async ({ connectionId }) => {
238-
this.log(`Sending message to client: ${connectionId.S}`);
239-
240-
try {
241-
const postToConnectionCommand = new PostToConnectionCommand({
242-
ConnectionId: connectionId.S,
243-
Data: JSON.stringify({
244-
timestamp: new Date().toISOString(),
245-
serviceKey: spyMessage.serviceKey,
246-
data: spyMessage.data,
247-
}) as any,
248-
});
249-
250-
await this.apigwManagementApi.send(postToConnectionCommand);
251-
} catch (e) {
252-
this.logError(`Faild sending spy message to: ${connectionId.S}`, e);
253-
if ((e as any).$metadata.httpStatusCode === 410) {
254-
this.log(`Found stale connection, deleting ${connectionId}`);
255-
256-
const deleteParams = new DeleteItemCommand({
257-
TableName: process.env[envVariableNames.SSPY_WS_TABLE_NAME],
258-
Key: { connectionId },
259-
});
241+
this.log('Post spy message', JSON.stringify(spyMessage));
260242

261-
await this.ddb.send(deleteParams);
262-
} else {
263-
throw e;
264-
}
243+
const connection = this.connection;
244+
const topic = `${SSPY_TOPIC}/${this.scope}`;
245+
246+
try {
247+
for (const fragment of this.encode(spyMessage)) {
248+
await new Promise<void>((resolve) => {
249+
connection.publish(
250+
topic,
251+
JSON.stringify(fragment),
252+
{
253+
qos: 1,
254+
},
255+
() => {
256+
console.error('Publishing finished');
257+
resolve();
258+
}
259+
);
260+
});
261+
this.log(
262+
`Published fragment ${fragment.index} out of ${fragment.count} to topic ${topic}`
263+
);
265264
}
266-
});
265+
} catch (e) {
266+
this.logError(`Failed to send payload to iot: ${e}`);
267+
}
267268

268-
await Promise.all(postCalls);
269269
this.log('Send spy message finish');
270270
}
271271

common/spyEvents/FunctionConsole.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export type FunctionConsole = {
22
type: 'log' | 'debug' | 'info' | 'error' | 'warn';
3+
formattedMessage?: string;
34
message?: any;
45
optionalParams: any[];
56
};

extension/interceptor.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interceptConsole();
3131
const spyEventSender = new SpyEventSender({
3232
log,
3333
logError,
34+
scope: process.env['SSPY_ROOT_STACK']!,
3435
});
3536

3637
// Wrap original handler.
@@ -41,6 +42,8 @@ export const handler = async (
4142
context: Context,
4243
callback: Callback
4344
): Promise<any | undefined> => {
45+
await spyEventSender.connect();
46+
4447
const contextSpy: FunctionContext = {
4548
functionName: context.functionName,
4649
awsRequestId: context.awsRequestId,
@@ -138,11 +141,14 @@ export const handler = async (
138141
}
139142
} catch (error) {
140143
// Even if the original handler is not async, we return the promise as an async handler so we can send an error message
144+
// eslint-disable-next-line @typescript-eslint/return-await
141145
return new Promise((_, reject) =>
142146
fail(error).then(() => {
143147
reject(error);
144148
})
145149
);
150+
} finally {
151+
await spyEventSender.close();
146152
}
147153
};
148154

extensions/python/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)