@datadog/datadog-ci-plugin-lambda 3.21.0 → 3.21.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cli.d.ts +1 -0
  2. package/dist/cli.js +7 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/commands/flare.d.ts +82 -0
  5. package/dist/commands/flare.js +636 -0
  6. package/dist/commands/flare.js.map +1 -0
  7. package/dist/commands/instrument.d.ts +10 -0
  8. package/dist/commands/instrument.js +405 -0
  9. package/dist/commands/instrument.js.map +1 -0
  10. package/dist/commands/uninstrument.d.ts +8 -0
  11. package/dist/commands/uninstrument.js +264 -0
  12. package/dist/commands/uninstrument.js.map +1 -0
  13. package/dist/constants.d.ts +87 -0
  14. package/dist/constants.js +159 -0
  15. package/dist/constants.js.map +1 -0
  16. package/dist/functions/commons.d.ts +131 -0
  17. package/dist/functions/commons.js +473 -0
  18. package/dist/functions/commons.js.map +1 -0
  19. package/dist/functions/instrument.d.ts +7 -0
  20. package/dist/functions/instrument.js +271 -0
  21. package/dist/functions/instrument.js.map +1 -0
  22. package/dist/functions/uninstrument.d.ts +7 -0
  23. package/dist/functions/uninstrument.js +156 -0
  24. package/dist/functions/uninstrument.js.map +1 -0
  25. package/dist/functions/versionChecker.d.ts +3 -0
  26. package/dist/functions/versionChecker.js +38 -0
  27. package/dist/functions/versionChecker.js.map +1 -0
  28. package/dist/interfaces.d.ts +91 -0
  29. package/dist/interfaces.js +3 -0
  30. package/dist/interfaces.js.map +1 -0
  31. package/dist/loggroup.d.ts +17 -0
  32. package/dist/loggroup.js +140 -0
  33. package/dist/loggroup.js.map +1 -0
  34. package/dist/prompt.d.ts +12 -0
  35. package/dist/prompt.js +265 -0
  36. package/dist/prompt.js.map +1 -0
  37. package/dist/renderers/__mocks__/instrument-uninstrument-renderer.d.ts +2 -0
  38. package/dist/renderers/__mocks__/instrument-uninstrument-renderer.js +11 -0
  39. package/dist/renderers/__mocks__/instrument-uninstrument-renderer.js.map +1 -0
  40. package/dist/renderers/common-renderer.d.ts +16 -0
  41. package/dist/renderers/common-renderer.js +23 -0
  42. package/dist/renderers/common-renderer.js.map +1 -0
  43. package/dist/renderers/instrument-uninstrument-renderer.d.ts +397 -0
  44. package/dist/renderers/instrument-uninstrument-renderer.js +506 -0
  45. package/dist/renderers/instrument-uninstrument-renderer.js.map +1 -0
  46. package/dist/tags.d.ts +8 -0
  47. package/dist/tags.js +74 -0
  48. package/dist/tags.js.map +1 -0
  49. package/package.json +9 -3
@@ -0,0 +1,636 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.generateInsightsFile = exports.getFramework = exports.sleep = exports.convertToCSV = exports.getTags = exports.getAllLogs = exports.getLogEvents = exports.getLogStreamNames = exports.summarizeConfig = exports.PluginCommand = void 0;
39
+ const fs = __importStar(require("fs"));
40
+ const util_1 = __importDefault(require("util"));
41
+ const client_cloudwatch_logs_1 = require("@aws-sdk/client-cloudwatch-logs");
42
+ const client_lambda_1 = require("@aws-sdk/client-lambda");
43
+ const flare_1 = require("@datadog/datadog-ci-base/commands/lambda/flare");
44
+ const constants_1 = require("@datadog/datadog-ci-base/constants");
45
+ const env_1 = require("@datadog/datadog-ci-base/helpers/env");
46
+ const fips_1 = require("@datadog/datadog-ci-base/helpers/fips");
47
+ const flare_2 = require("@datadog/datadog-ci-base/helpers/flare");
48
+ const fs_1 = require("@datadog/datadog-ci-base/helpers/fs");
49
+ const prompt_1 = require("@datadog/datadog-ci-base/helpers/prompt");
50
+ const helpersRenderer = __importStar(require("@datadog/datadog-ci-base/helpers/renderer"));
51
+ const renderer_1 = require("@datadog/datadog-ci-base/helpers/renderer");
52
+ const utils_1 = require("@datadog/datadog-ci-base/helpers/utils");
53
+ const version_1 = require("@datadog/datadog-ci-base/version");
54
+ const chalk_1 = __importDefault(require("chalk"));
55
+ const upath_1 = __importDefault(require("upath"));
56
+ const constants_2 = require("../constants");
57
+ const commons_1 = require("../functions/commons");
58
+ const prompt_2 = require("../prompt");
59
+ const commonRenderer = __importStar(require("../renderers/common-renderer"));
60
+ const FUNCTION_CONFIG_FILE_NAME = 'function_config.json';
61
+ const TAGS_FILE_NAME = 'tags.json';
62
+ const FLARE_ZIP_FILE_NAME = 'lambda-flare-output.zip';
63
+ const MAX_LOG_STREAMS = 50;
64
+ const DEFAULT_LOG_STREAMS = 3;
65
+ const MAX_LOG_EVENTS_PER_STREAM = 1000;
66
+ const SUMMARIZED_FIELDS = new Set(['FunctionName', 'Runtime', 'FunctionArn', 'Handler', 'Environment']);
67
+ class PluginCommand extends flare_1.LambdaFlareCommand {
68
+ constructor() {
69
+ var _a, _b;
70
+ super(...arguments);
71
+ this.config = {
72
+ fips: (_a = (0, env_1.toBoolean)(process.env[constants_1.FIPS_ENV_VAR])) !== null && _a !== void 0 ? _a : false,
73
+ fipsIgnoreError: (_b = (0, env_1.toBoolean)(process.env[constants_1.FIPS_IGNORE_ERROR_ENV_VAR])) !== null && _b !== void 0 ? _b : false,
74
+ };
75
+ }
76
+ /**
77
+ * Entry point for the `lambda flare` command.
78
+ * Gathers config, logs, tags, project files, and more from a
79
+ * Lambda function and sends them to Datadog support.
80
+ * @returns 0 if the command ran successfully, 1 otherwise.
81
+ */
82
+ execute() {
83
+ var _a, _b, _c;
84
+ return __awaiter(this, void 0, void 0, function* () {
85
+ (0, fips_1.enableFips)(this.fips || this.config.fips, this.fipsIgnoreError || this.config.fipsIgnoreError);
86
+ yield (0, flare_2.validateCliVersion)(version_1.cliVersion, this.context.stdout);
87
+ this.context.stdout.write(helpersRenderer.renderFlareHeader('Lambda', this.isDryRun));
88
+ // Validate function name
89
+ if (this.functionName === undefined) {
90
+ this.context.stderr.write(helpersRenderer.renderError('No function name specified. [-f,--function]'));
91
+ return 1;
92
+ }
93
+ const errorMessages = [];
94
+ // Validate region
95
+ const region = (_b = (_a = (0, commons_1.getRegion)(this.functionName)) !== null && _a !== void 0 ? _a : this.region) !== null && _b !== void 0 ? _b : process.env[constants_2.AWS_DEFAULT_REGION_ENV_VAR];
96
+ if (region === undefined) {
97
+ errorMessages.push(commonRenderer.renderNoDefaultRegionSpecifiedError());
98
+ }
99
+ // Validate Datadog API key
100
+ this.apiKey = (_c = process.env[constants_1.CI_API_KEY_ENV_VAR]) !== null && _c !== void 0 ? _c : process.env[constants_1.API_KEY_ENV_VAR];
101
+ if (this.apiKey === undefined) {
102
+ errorMessages.push(helpersRenderer.renderError('No Datadog API key specified. Set an API key with the DD_API_KEY environment variable.'));
103
+ }
104
+ // Validate case ID
105
+ if (this.caseId === undefined) {
106
+ errorMessages.push(helpersRenderer.renderError('No case ID specified. [-c,--case-id]'));
107
+ }
108
+ // Validate email
109
+ if (this.email === undefined) {
110
+ errorMessages.push(helpersRenderer.renderError('No email specified. [-e,--email]'));
111
+ }
112
+ // Validate start/end flags if both are specified
113
+ let startMillis;
114
+ let endMillis;
115
+ try {
116
+ ;
117
+ [startMillis, endMillis] = (0, flare_2.validateStartEndFlags)(this.start, this.end);
118
+ }
119
+ catch (err) {
120
+ if (err instanceof Error) {
121
+ errorMessages.push(helpersRenderer.renderError(err.message));
122
+ }
123
+ }
124
+ if (errorMessages.length > 0) {
125
+ for (const message of errorMessages) {
126
+ this.context.stderr.write(message);
127
+ }
128
+ return 1;
129
+ }
130
+ // Get AWS credentials
131
+ this.context.stdout.write(chalk_1.default.bold('\n🔑 Getting AWS credentials...\n'));
132
+ try {
133
+ this.credentials = yield (0, commons_1.getAWSCredentials)();
134
+ }
135
+ catch (err) {
136
+ if (err instanceof Error) {
137
+ this.context.stderr.write(helpersRenderer.renderError(err.message));
138
+ }
139
+ return 1;
140
+ }
141
+ if (this.credentials === undefined) {
142
+ this.context.stdout.write('\n' + commonRenderer.renderNoAWSCredentialsFound());
143
+ try {
144
+ yield (0, prompt_2.requestAWSCredentials)();
145
+ }
146
+ catch (err) {
147
+ if (err instanceof Error) {
148
+ this.context.stderr.write(helpersRenderer.renderError(err.message));
149
+ }
150
+ return 1;
151
+ }
152
+ }
153
+ // Get and print Lambda function configuration
154
+ this.context.stdout.write(chalk_1.default.bold('\n🔍 Fetching Lambda function configuration...\n'));
155
+ const lambdaClientConfig = {
156
+ region,
157
+ credentials: this.credentials,
158
+ retryStrategy: constants_2.EXPONENTIAL_BACKOFF_RETRY_STRATEGY,
159
+ };
160
+ const lambdaClient = new client_lambda_1.LambdaClient(lambdaClientConfig);
161
+ let config;
162
+ try {
163
+ config = yield (0, commons_1.getLambdaFunctionConfig)(lambdaClient, this.functionName);
164
+ }
165
+ catch (err) {
166
+ if (err instanceof Error) {
167
+ this.context.stderr.write(helpersRenderer.renderError(`Unable to get Lambda function configuration: ${err.message}`));
168
+ }
169
+ return 1;
170
+ }
171
+ config = (0, commons_1.maskConfig)(config);
172
+ const summarizedConfig = (0, exports.summarizeConfig)(config);
173
+ const summarizedConfigStr = util_1.default.inspect(summarizedConfig, false, undefined, true);
174
+ this.context.stdout.write(`\n${summarizedConfigStr}\n`);
175
+ this.context.stdout.write(chalk_1.default.italic(`(This is a summary of the configuration. The full configuration will be saved in "${FUNCTION_CONFIG_FILE_NAME}".)\n`));
176
+ // Get project files
177
+ this.context.stdout.write(chalk_1.default.bold('\n📁 Searching for project files in current directory...\n'));
178
+ const projectFilePaths = yield (0, flare_2.getProjectFiles)(constants_2.LAMBDA_PROJECT_FILES);
179
+ this.context.stdout.write((0, renderer_1.renderProjectFiles)(projectFilePaths));
180
+ // Additional files
181
+ this.context.stdout.write('\n');
182
+ const additionalFilePaths = new Set();
183
+ let confirmAdditionalFiles;
184
+ try {
185
+ confirmAdditionalFiles = yield (0, prompt_1.requestConfirmation)('Do you want to specify any additional files to flare?', false);
186
+ }
187
+ catch (err) {
188
+ if (err instanceof Error) {
189
+ this.context.stderr.write(helpersRenderer.renderError(err.message));
190
+ }
191
+ return 1;
192
+ }
193
+ while (confirmAdditionalFiles) {
194
+ this.context.stdout.write('\n');
195
+ let filePath;
196
+ try {
197
+ filePath = yield (0, prompt_1.requestFilePath)();
198
+ }
199
+ catch (err) {
200
+ if (err instanceof Error) {
201
+ this.context.stderr.write(helpersRenderer.renderError(err.message));
202
+ }
203
+ return 1;
204
+ }
205
+ if (filePath === '') {
206
+ this.context.stdout.write((0, renderer_1.renderAdditionalFiles)(additionalFilePaths));
207
+ break;
208
+ }
209
+ try {
210
+ filePath = (0, flare_2.validateFilePath)(filePath, projectFilePaths, additionalFilePaths);
211
+ additionalFilePaths.add(filePath);
212
+ const fileName = upath_1.default.basename(filePath);
213
+ this.context.stdout.write(`• Added file '${fileName}'\n`);
214
+ }
215
+ catch (err) {
216
+ if (err instanceof Error) {
217
+ this.context.stderr.write(err.message);
218
+ }
219
+ }
220
+ }
221
+ // Get tags
222
+ this.context.stdout.write(chalk_1.default.bold('\n🏷 Getting Resource Tags...\n'));
223
+ let tags;
224
+ try {
225
+ tags = yield (0, exports.getTags)(lambdaClient, region, config.FunctionArn);
226
+ }
227
+ catch (err) {
228
+ if (err instanceof Error) {
229
+ this.context.stderr.write(helpersRenderer.renderError(err.message));
230
+ }
231
+ return 1;
232
+ }
233
+ const tagsLength = Object.keys(tags).length;
234
+ if (tagsLength === 0) {
235
+ this.context.stdout.write(helpersRenderer.renderSoftWarning(`No resource tags were found.`));
236
+ }
237
+ else {
238
+ this.context.stdout.write(`Found ${tagsLength} resource tag(s).\n`);
239
+ }
240
+ // Get CloudWatch logs
241
+ let logs = new Map();
242
+ if (this.withLogs) {
243
+ this.context.stdout.write(chalk_1.default.bold('\n🌧 Getting CloudWatch logs...\n'));
244
+ try {
245
+ logs = yield (0, exports.getAllLogs)(region, this.functionName, startMillis, endMillis);
246
+ }
247
+ catch (err) {
248
+ if (err instanceof Error) {
249
+ this.context.stderr.write(helpersRenderer.renderError(err.message));
250
+ }
251
+ return 1;
252
+ }
253
+ }
254
+ try {
255
+ // CloudWatch messages
256
+ if (this.withLogs) {
257
+ let message = chalk_1.default.bold('\n✅ Found log streams:\n');
258
+ if (logs.size === 0) {
259
+ message = helpersRenderer.renderSoftWarning('No CloudWatch log streams were found. Logs will not be retrieved or sent.');
260
+ }
261
+ this.context.stdout.write(message);
262
+ for (const [logStreamName, logEvents] of logs) {
263
+ let warningMessage = '\n';
264
+ if (logEvents.length === 0) {
265
+ warningMessage = ` - ${helpersRenderer.renderSoftWarning('No log events found in this stream')}`;
266
+ }
267
+ this.context.stdout.write(`• ${logStreamName}${warningMessage}`);
268
+ }
269
+ }
270
+ // Create folders
271
+ const rootFolderPath = upath_1.default.join(process.cwd(), constants_1.FLARE_OUTPUT_DIRECTORY);
272
+ const logsFolderPath = upath_1.default.join(rootFolderPath, constants_1.LOGS_DIRECTORY);
273
+ const projectFilesFolderPath = upath_1.default.join(rootFolderPath, constants_1.PROJECT_FILES_DIRECTORY);
274
+ const additionalFilesFolderPath = upath_1.default.join(rootFolderPath, constants_1.ADDITIONAL_FILES_DIRECTORY);
275
+ this.context.stdout.write(chalk_1.default.bold(`\n💾 Saving files to ${rootFolderPath}...\n`));
276
+ if (fs.existsSync(rootFolderPath)) {
277
+ (0, fs_1.deleteFolder)(rootFolderPath);
278
+ }
279
+ const subFolders = [];
280
+ if (logs.size > 0) {
281
+ subFolders.push(logsFolderPath);
282
+ }
283
+ if (projectFilePaths.size > 0) {
284
+ subFolders.push(projectFilesFolderPath);
285
+ }
286
+ if (additionalFilePaths.size > 0) {
287
+ subFolders.push(additionalFilesFolderPath);
288
+ }
289
+ (0, fs_1.createDirectories)(rootFolderPath, subFolders);
290
+ // Write config file
291
+ const configFilePath = upath_1.default.join(rootFolderPath, FUNCTION_CONFIG_FILE_NAME);
292
+ (0, fs_1.writeFile)(configFilePath, JSON.stringify(config, undefined, 2));
293
+ this.context.stdout.write(`• Saved function config to ./${FUNCTION_CONFIG_FILE_NAME}\n`);
294
+ // Write tags file
295
+ if (tagsLength > 0) {
296
+ const tagsFilePath = upath_1.default.join(rootFolderPath, TAGS_FILE_NAME);
297
+ (0, fs_1.writeFile)(tagsFilePath, JSON.stringify(tags, undefined, 2));
298
+ this.context.stdout.write(`• Saved tags to ./${TAGS_FILE_NAME}\n`);
299
+ }
300
+ // Write log files
301
+ for (const [logStreamName, logEvents] of logs) {
302
+ if (logEvents.length === 0) {
303
+ continue;
304
+ }
305
+ const logFilePath = upath_1.default.join(logsFolderPath, `${logStreamName.split('/').join('-')}.csv`);
306
+ const data = (0, exports.convertToCSV)(logEvents);
307
+ (0, fs_1.writeFile)(logFilePath, data);
308
+ this.context.stdout.write(`• Saved logs to ./${constants_1.LOGS_DIRECTORY}/${logStreamName}\n`);
309
+ // Sleep for 1 millisecond so creation times are different
310
+ // This allows the logs to be sorted by creation time by the support team
311
+ yield (0, exports.sleep)(1);
312
+ }
313
+ // Write project files
314
+ for (const filePath of projectFilePaths) {
315
+ const fileName = upath_1.default.basename(filePath);
316
+ const newFilePath = upath_1.default.join(projectFilesFolderPath, fileName);
317
+ fs.copyFileSync(filePath, newFilePath);
318
+ this.context.stdout.write(`• Copied ${fileName} to ./${constants_1.PROJECT_FILES_DIRECTORY}/${fileName}\n`);
319
+ }
320
+ // Write additional files
321
+ const additionalFilesMap = (0, flare_2.getUniqueFileNames)(additionalFilePaths);
322
+ for (const [originalFilePath, newFileName] of additionalFilesMap) {
323
+ const originalFileName = upath_1.default.basename(originalFilePath);
324
+ const newFilePath = upath_1.default.join(additionalFilesFolderPath, newFileName);
325
+ fs.copyFileSync(originalFilePath, newFilePath);
326
+ this.context.stdout.write(`• Copied ${originalFileName} to ./${constants_1.ADDITIONAL_FILES_DIRECTORY}/${newFileName}\n`);
327
+ }
328
+ // Write insights file
329
+ try {
330
+ const insightsFilePath = upath_1.default.join(rootFolderPath, constants_1.INSIGHTS_FILE_NAME);
331
+ (0, exports.generateInsightsFile)(insightsFilePath, this.isDryRun, config);
332
+ this.context.stdout.write(`• Saved the insights file to ./${constants_1.INSIGHTS_FILE_NAME}\n`);
333
+ }
334
+ catch (err) {
335
+ const errorDetails = err instanceof Error ? err.message : '';
336
+ this.context.stdout.write(helpersRenderer.renderSoftWarning(`Unable to create INSIGHTS.md file. ${errorDetails}`));
337
+ }
338
+ // Exit if dry run
339
+ const outputMsg = `\nℹ️ Your output files are located at: ${rootFolderPath}\n\n`;
340
+ if (this.isDryRun) {
341
+ this.context.stdout.write('\n🚫 The flare files were not sent because the command was executed in dry run mode.');
342
+ this.context.stdout.write(outputMsg);
343
+ return 0;
344
+ }
345
+ // Confirm before sending
346
+ this.context.stdout.write('\n');
347
+ const confirmSendFiles = yield (0, prompt_1.requestConfirmation)('Are you sure you want to send the flare file to Datadog Support?', false);
348
+ if (!confirmSendFiles) {
349
+ this.context.stdout.write('\n🚫 The flare files were not sent based on your selection.');
350
+ this.context.stdout.write(outputMsg);
351
+ return 0;
352
+ }
353
+ // Zip folder
354
+ const zipPath = upath_1.default.join(rootFolderPath, FLARE_ZIP_FILE_NAME);
355
+ yield (0, fs_1.zipContents)(rootFolderPath, zipPath);
356
+ // Send to Datadog
357
+ this.context.stdout.write(chalk_1.default.bold('\n🚀 Sending to Datadog Support...\n'));
358
+ yield (0, flare_2.sendToDatadog)(zipPath, this.caseId, this.email, this.apiKey, rootFolderPath, version_1.cliVersion);
359
+ this.context.stdout.write(chalk_1.default.bold('\n✅ Successfully sent flare file to Datadog Support!\n'));
360
+ // Delete contents
361
+ (0, fs_1.deleteFolder)(rootFolderPath);
362
+ }
363
+ catch (err) {
364
+ if (err instanceof Error) {
365
+ this.context.stderr.write(helpersRenderer.renderError(err.message));
366
+ }
367
+ return 1;
368
+ }
369
+ return 0;
370
+ });
371
+ }
372
+ }
373
+ exports.PluginCommand = PluginCommand;
374
+ /**
375
+ * Summarizes the Lambda config as to not flood the terminal
376
+ * @param config
377
+ * @returns a summarized config
378
+ */
379
+ const summarizeConfig = (config) => {
380
+ const summarizedConfig = {};
381
+ for (const key in config) {
382
+ if (SUMMARIZED_FIELDS.has(key)) {
383
+ summarizedConfig[key] = config[key];
384
+ }
385
+ }
386
+ return summarizedConfig;
387
+ };
388
+ exports.summarizeConfig = summarizeConfig;
389
+ /**
390
+ * Gets the LOG_STREAM_COUNT latest log stream names, sorted by last event time
391
+ * @param cwlClient CloudWatch Logs client
392
+ * @param logGroupName name of the log group
393
+ * @param startMillis start time in milliseconds or undefined if no start time is specified
394
+ * @param endMillis end time in milliseconds or undefined if no end time is specified
395
+ * @returns an array of the last LOG_STREAM_COUNT log stream names or an empty array if no log streams are found
396
+ * @throws Error if the log streams cannot be retrieved
397
+ */
398
+ const getLogStreamNames = (cwlClient, logGroupName, startMillis, endMillis) => __awaiter(void 0, void 0, void 0, function* () {
399
+ const config = {
400
+ logGroupName,
401
+ descending: true,
402
+ orderBy: client_cloudwatch_logs_1.OrderBy.LastEventTime,
403
+ limit: DEFAULT_LOG_STREAMS,
404
+ };
405
+ const rangeSpecified = startMillis !== undefined && endMillis !== undefined;
406
+ if (rangeSpecified) {
407
+ config.limit = MAX_LOG_STREAMS;
408
+ }
409
+ const command = new client_cloudwatch_logs_1.DescribeLogStreamsCommand(config);
410
+ const response = yield cwlClient.send(command);
411
+ const logStreams = response.logStreams;
412
+ if (logStreams === undefined || logStreams.length === 0) {
413
+ return [];
414
+ }
415
+ const output = [];
416
+ for (const logStream of logStreams) {
417
+ const logStreamName = logStream.logStreamName;
418
+ if (!logStreamName) {
419
+ continue;
420
+ }
421
+ if (rangeSpecified) {
422
+ const firstEventTime = logStream.firstEventTimestamp;
423
+ const lastEventTime = logStream.lastEventTimestamp;
424
+ if (lastEventTime && lastEventTime < startMillis) {
425
+ continue;
426
+ }
427
+ if (firstEventTime && firstEventTime > endMillis) {
428
+ continue;
429
+ }
430
+ }
431
+ output.push(logStreamName);
432
+ }
433
+ // Reverse array so the oldest log is created first, so Support Staff can sort by creation time
434
+ return output.reverse();
435
+ });
436
+ exports.getLogStreamNames = getLogStreamNames;
437
+ /**
438
+ * Gets the log events for a log stream
439
+ * @param cwlClient
440
+ * @param logGroupName
441
+ * @param logStreamName
442
+ * @param startMillis
443
+ * @param endMillis
444
+ * @returns the log events or an empty array if no log events are found
445
+ * @throws Error if the log events cannot be retrieved
446
+ */
447
+ const getLogEvents = (cwlClient, logGroupName, logStreamName, startMillis, endMillis) => __awaiter(void 0, void 0, void 0, function* () {
448
+ const config = {
449
+ logGroupName,
450
+ logStreamName,
451
+ limit: MAX_LOG_EVENTS_PER_STREAM,
452
+ };
453
+ if (startMillis !== undefined && endMillis !== undefined) {
454
+ config.startTime = startMillis;
455
+ config.endTime = endMillis;
456
+ }
457
+ const command = new client_cloudwatch_logs_1.GetLogEventsCommand(config);
458
+ const response = yield cwlClient.send(command);
459
+ const logEvents = response.events;
460
+ if (logEvents === undefined) {
461
+ return [];
462
+ }
463
+ return logEvents;
464
+ });
465
+ exports.getLogEvents = getLogEvents;
466
+ /**
467
+ * Gets all CloudWatch logs for a function
468
+ * @param region
469
+ * @param functionName
470
+ * @param startMillis start time in milliseconds or undefined if no end time is specified
471
+ * @param endMillis end time in milliseconds or undefined if no end time is specified
472
+ * @returns a map of log stream names to log events or an empty map if no logs are found
473
+ */
474
+ const getAllLogs = (region, functionName, startMillis, endMillis) => __awaiter(void 0, void 0, void 0, function* () {
475
+ const logs = new Map();
476
+ const cwlClient = new client_cloudwatch_logs_1.CloudWatchLogsClient({ region, retryStrategy: constants_2.EXPONENTIAL_BACKOFF_RETRY_STRATEGY });
477
+ if (functionName.startsWith('arn:aws')) {
478
+ functionName = functionName.split(':')[6];
479
+ }
480
+ const logGroupName = `/aws/lambda/${functionName}`;
481
+ let logStreamNames;
482
+ try {
483
+ logStreamNames = yield (0, exports.getLogStreamNames)(cwlClient, logGroupName, startMillis, endMillis);
484
+ }
485
+ catch (err) {
486
+ const msg = err instanceof Error ? err.message : '';
487
+ throw new Error(`Unable to get log streams: ${msg}`);
488
+ }
489
+ for (const logStreamName of logStreamNames) {
490
+ let logEvents;
491
+ try {
492
+ logEvents = yield (0, exports.getLogEvents)(cwlClient, logGroupName, logStreamName, startMillis, endMillis);
493
+ }
494
+ catch (err) {
495
+ const msg = err instanceof Error ? err.message : '';
496
+ throw new Error(`Unable to get log events for stream ${logStreamName}: ${msg}`);
497
+ }
498
+ logs.set(logStreamName, logEvents);
499
+ }
500
+ return logs;
501
+ });
502
+ exports.getAllLogs = getAllLogs;
503
+ /**
504
+ * Gets the tags for a function
505
+ * @param lambdaClient
506
+ * @param region
507
+ * @param arn
508
+ * @returns the tags or an empty object if no tags are found
509
+ * @throws Error if the tags cannot be retrieved
510
+ */
511
+ const getTags = (lambdaClient, region, arn) => __awaiter(void 0, void 0, void 0, function* () {
512
+ var _a;
513
+ if (!arn.startsWith('arn:aws')) {
514
+ throw Error(`Invalid function ARN: ${arn}`);
515
+ }
516
+ const command = new client_lambda_1.ListTagsCommand({
517
+ Resource: arn,
518
+ });
519
+ try {
520
+ const response = yield lambdaClient.send(command);
521
+ return (_a = response.Tags) !== null && _a !== void 0 ? _a : {};
522
+ }
523
+ catch (err) {
524
+ let message = '';
525
+ if (err instanceof Error) {
526
+ message = err.message;
527
+ }
528
+ throw Error(`Unable to get resource tags: ${message}`);
529
+ }
530
+ });
531
+ exports.getTags = getTags;
532
+ /**
533
+ * Convert the log events to a CSV string
534
+ * @param logEvents array of log events
535
+ * @returns the CSV string
536
+ */
537
+ const convertToCSV = (logEvents) => {
538
+ var _a, _b;
539
+ const rows = [['timestamp', 'datetime', 'message']];
540
+ for (const logEvent of logEvents) {
541
+ const timestamp = `"${(_a = logEvent.timestamp) !== null && _a !== void 0 ? _a : ''}"`;
542
+ let datetime = '';
543
+ if (logEvent.timestamp) {
544
+ const date = new Date(logEvent.timestamp);
545
+ datetime = date.toISOString().replace('T', ' ').replace('Z', '');
546
+ }
547
+ const message = `"${(_b = logEvent.message) !== null && _b !== void 0 ? _b : ''}"`;
548
+ rows.push([timestamp, datetime, message]);
549
+ }
550
+ return rows.join('\n');
551
+ };
552
+ exports.convertToCSV = convertToCSV;
553
+ /**
554
+ * @param ms number of milliseconds to sleep
555
+ */
556
+ const sleep = (ms) => __awaiter(void 0, void 0, void 0, function* () {
557
+ yield new Promise((resolve) => setTimeout(resolve, ms));
558
+ });
559
+ exports.sleep = sleep;
560
+ /**
561
+ * Get the framework used based on the files in the directory
562
+ * @returns the framework used or undefined if no framework is found
563
+ */
564
+ const getFramework = () => {
565
+ const frameworks = new Set();
566
+ const files = fs.readdirSync(process.cwd());
567
+ files.forEach((file) => {
568
+ if (constants_2.FRAMEWORK_FILES_MAPPING.has(file)) {
569
+ frameworks.add(constants_2.FRAMEWORK_FILES_MAPPING.get(file));
570
+ }
571
+ });
572
+ if (frameworks.size > 0) {
573
+ return Array.from(frameworks).join(', ');
574
+ }
575
+ return constants_2.DeploymentFrameworks.Unknown;
576
+ };
577
+ exports.getFramework = getFramework;
578
+ /**
579
+ * Generate the insights file
580
+ * @param insightsFilePath path to the insights file
581
+ * @param isDryRun whether or not this is a dry run
582
+ * @param config Lambda function configuration
583
+ */
584
+ const generateInsightsFile = (insightsFilePath, isDryRun, config) => {
585
+ var _a, _b, _c, _d, _e;
586
+ const lines = [];
587
+ // Header
588
+ lines.push('# Flare Insights');
589
+ lines.push('\n_Autogenerated file from `lambda flare`_ ');
590
+ if (isDryRun) {
591
+ lines.push('_This command was run in dry mode._');
592
+ }
593
+ // AWS Lambda Configuration
594
+ lines.push('\n## AWS Lambda Configuration');
595
+ lines.push(`**Function Name**: \`${config.FunctionName}\` `);
596
+ lines.push(`**Function ARN**: \`${config.FunctionArn}\` `);
597
+ lines.push(`**Runtime**: \`${config.Runtime}\` `);
598
+ lines.push(`**Handler**: \`${config.Handler}\` `);
599
+ lines.push(`**Timeout**: \`${config.Timeout}\` `);
600
+ lines.push(`**Memory Size**: \`${config.MemorySize}\` `);
601
+ const architectures = (_a = config.Architectures) !== null && _a !== void 0 ? _a : ['Unknown'];
602
+ lines.push(`**Architecture**: \`${architectures.join(', ')}\` `);
603
+ lines.push('**Environment Variables**:');
604
+ const envVars = Object.entries((_c = (_b = config.Environment) === null || _b === void 0 ? void 0 : _b.Variables) !== null && _c !== void 0 ? _c : {});
605
+ if (envVars.length === 0) {
606
+ lines.push('- No environment variables found.');
607
+ }
608
+ for (const [key, value] of envVars) {
609
+ lines.push(`- \`${key}\`: \`${value}\``);
610
+ }
611
+ lines.push('\n**Layers**:');
612
+ const layers = (_d = config.Layers) !== null && _d !== void 0 ? _d : [];
613
+ if (layers.length === 0) {
614
+ lines.push(' - No layers found.');
615
+ }
616
+ let codeSize = (_e = config.CodeSize) !== null && _e !== void 0 ? _e : 0;
617
+ layers.forEach((layer) => {
618
+ var _a, _b;
619
+ const nameAndVersion = (0, commons_1.getLayerNameWithVersion)((_a = layer.Arn) !== null && _a !== void 0 ? _a : '');
620
+ if (nameAndVersion) {
621
+ lines.push(`- \`${nameAndVersion}\``);
622
+ }
623
+ codeSize += (_b = layer.CodeSize) !== null && _b !== void 0 ? _b : 0;
624
+ });
625
+ lines.push(`\n**Package Size**: \`${(0, utils_1.formatBytes)(codeSize)}\``);
626
+ // CLI Insights
627
+ lines.push('\n ## CLI');
628
+ lines.push(`**Run Location**: \`${process.cwd()}\` `);
629
+ lines.push(`**CLI Version**: \`${version_1.cliVersion}\` `);
630
+ const timeString = new Date().toISOString().replace('T', ' ').replace('Z', '') + ' UTC';
631
+ lines.push(`**Timestamp**: \`${timeString}\` `);
632
+ lines.push(`**Framework**: \`${(0, exports.getFramework)()}\``);
633
+ (0, fs_1.writeFile)(insightsFilePath, lines.join('\n'));
634
+ };
635
+ exports.generateInsightsFile = generateInsightsFile;
636
+ //# sourceMappingURL=flare.js.map