@datadog/datadog-ci 2.16.0 → 2.17.0

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 (80) hide show
  1. package/dist/commands/git-metadata/git.d.ts +0 -1
  2. package/dist/commands/git-metadata/git.js +5 -20
  3. package/dist/commands/git-metadata/git.js.map +1 -1
  4. package/dist/commands/git-metadata/gitdb.js +31 -21
  5. package/dist/commands/git-metadata/gitdb.js.map +1 -1
  6. package/dist/commands/junit/api.js +16 -13
  7. package/dist/commands/junit/api.js.map +1 -1
  8. package/dist/commands/junit/interfaces.d.ts +4 -1
  9. package/dist/commands/junit/upload.d.ts +6 -0
  10. package/dist/commands/junit/upload.js +35 -13
  11. package/dist/commands/junit/upload.js.map +1 -1
  12. package/dist/commands/junit/utils.d.ts +1 -0
  13. package/dist/commands/junit/utils.js +11 -1
  14. package/dist/commands/junit/utils.js.map +1 -1
  15. package/dist/commands/lambda/cli.js +2 -1
  16. package/dist/commands/lambda/cli.js.map +1 -1
  17. package/dist/commands/lambda/constants.d.ts +1 -0
  18. package/dist/commands/lambda/constants.js +17 -1
  19. package/dist/commands/lambda/constants.js.map +1 -1
  20. package/dist/commands/lambda/flare.d.ts +127 -0
  21. package/dist/commands/lambda/flare.js +656 -0
  22. package/dist/commands/lambda/flare.js.map +1 -0
  23. package/dist/commands/lambda/functions/commons.d.ts +14 -1
  24. package/dist/commands/lambda/functions/commons.js +53 -11
  25. package/dist/commands/lambda/functions/commons.js.map +1 -1
  26. package/dist/commands/lambda/instrument.js +43 -41
  27. package/dist/commands/lambda/instrument.js.map +1 -1
  28. package/dist/commands/lambda/renderers/common-renderer.d.ts +42 -0
  29. package/dist/commands/lambda/renderers/common-renderer.js +51 -0
  30. package/dist/commands/lambda/renderers/common-renderer.js.map +1 -0
  31. package/dist/commands/lambda/renderers/flare-renderer.d.ts +9 -0
  32. package/dist/commands/lambda/renderers/flare-renderer.js +18 -0
  33. package/dist/commands/lambda/renderers/flare-renderer.js.map +1 -0
  34. package/dist/commands/lambda/{renderer.d.ts → renderers/instrument-uninstrument-renderer.d.ts} +2 -44
  35. package/dist/commands/lambda/{renderer.js → renderers/instrument-uninstrument-renderer.js} +30 -76
  36. package/dist/commands/lambda/renderers/instrument-uninstrument-renderer.js.map +1 -0
  37. package/dist/commands/lambda/uninstrument.js +31 -29
  38. package/dist/commands/lambda/uninstrument.js.map +1 -1
  39. package/dist/commands/react-native/renderer.js +2 -0
  40. package/dist/commands/react-native/renderer.js.map +1 -1
  41. package/dist/commands/sarif/api.js +1 -1
  42. package/dist/commands/sarif/api.js.map +1 -1
  43. package/dist/commands/synthetics/interfaces.d.ts +1 -1
  44. package/dist/commands/synthetics/reporters/default.d.ts +1 -1
  45. package/dist/commands/synthetics/reporters/default.js +3 -1
  46. package/dist/commands/synthetics/reporters/default.js.map +1 -1
  47. package/dist/commands/synthetics/run-tests-command.d.ts +1 -0
  48. package/dist/commands/synthetics/run-tests-command.js +9 -2
  49. package/dist/commands/synthetics/run-tests-command.js.map +1 -1
  50. package/dist/commands/synthetics/run-tests-lib.js +2 -5
  51. package/dist/commands/synthetics/run-tests-lib.js.map +1 -1
  52. package/dist/commands/synthetics/tunnel/tunnel.d.ts +2 -2
  53. package/dist/commands/synthetics/tunnel/tunnel.js.map +1 -1
  54. package/dist/commands/synthetics/tunnel/websocket.d.ts +2 -2
  55. package/dist/commands/synthetics/tunnel/websocket.js.map +1 -1
  56. package/dist/commands/synthetics/utils.d.ts +3 -0
  57. package/dist/commands/synthetics/utils.js +24 -9
  58. package/dist/commands/synthetics/utils.js.map +1 -1
  59. package/dist/commands/trace/trace.js +1 -1
  60. package/dist/commands/trace/trace.js.map +1 -1
  61. package/dist/helpers/ci.d.ts +1 -0
  62. package/dist/helpers/file.d.ts +1 -1
  63. package/dist/helpers/file.js +3 -6
  64. package/dist/helpers/file.js.map +1 -1
  65. package/dist/helpers/git/format-git-sourcemaps-data.js +2 -2
  66. package/dist/helpers/git/format-git-sourcemaps-data.js.map +1 -1
  67. package/dist/helpers/git/get-git-data.d.ts +1 -0
  68. package/dist/helpers/git/get-git-data.js +14 -4
  69. package/dist/helpers/git/get-git-data.js.map +1 -1
  70. package/dist/helpers/interfaces.d.ts +2 -2
  71. package/dist/helpers/metrics.js +2 -2
  72. package/dist/helpers/metrics.js.map +1 -1
  73. package/dist/helpers/tags.d.ts +1 -0
  74. package/dist/helpers/tags.js +2 -1
  75. package/dist/helpers/tags.js.map +1 -1
  76. package/dist/helpers/utils.d.ts +3 -2
  77. package/dist/helpers/utils.js +26 -3
  78. package/dist/helpers/utils.js.map +1 -1
  79. package/package.json +6 -4
  80. package/dist/commands/lambda/renderer.js.map +0 -1
@@ -0,0 +1,656 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
+ return new (P || (P = Promise))(function (resolve, reject) {
24
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ };
30
+ var __importDefault = (this && this.__importDefault) || function (mod) {
31
+ return (mod && mod.__esModule) ? mod : { "default": mod };
32
+ };
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.sendToDatadog = exports.getEndpointUrl = exports.zipContents = exports.sleep = exports.convertToCSV = exports.writeFile = exports.getTags = exports.getAllLogs = exports.getLogEvents = exports.getLogStreamNames = exports.createDirectories = exports.deleteFolder = exports.maskConfig = exports.validateStartEndFlags = exports.LambdaFlareCommand = void 0;
35
+ const fs = __importStar(require("fs"));
36
+ const path = __importStar(require("path"));
37
+ const util_1 = __importDefault(require("util"));
38
+ const client_cloudwatch_logs_1 = require("@aws-sdk/client-cloudwatch-logs");
39
+ const client_lambda_1 = require("@aws-sdk/client-lambda");
40
+ const axios_1 = __importDefault(require("axios"));
41
+ const clipanion_1 = require("clipanion");
42
+ const form_data_1 = __importDefault(require("form-data"));
43
+ const inquirer_1 = __importDefault(require("inquirer"));
44
+ const jszip_1 = __importDefault(require("jszip"));
45
+ const constants_1 = require("../../constants");
46
+ const validation_1 = require("../../helpers/validation");
47
+ const constants_2 = require("./constants");
48
+ const commons_1 = require("./functions/commons");
49
+ const prompt_1 = require("./prompt");
50
+ const commonRenderer = __importStar(require("./renderers/common-renderer"));
51
+ const flareRenderer = __importStar(require("./renderers/flare-renderer"));
52
+ const { version } = require('../../../package.json');
53
+ const ENDPOINT_PATH = '/api/ui/support/serverless/flare';
54
+ const FLARE_OUTPUT_DIRECTORY = '.datadog-ci';
55
+ const LOGS_DIRECTORY = 'logs';
56
+ const FUNCTION_CONFIG_FILE_NAME = 'function_config.json';
57
+ const TAGS_FILE_NAME = 'tags.json';
58
+ const ZIP_FILE_NAME = 'lambda-flare-output.zip';
59
+ const MAX_LOG_STREAMS = 50;
60
+ const DEFAULT_LOG_STREAMS = 3;
61
+ const MAX_LOG_EVENTS_PER_STREAM = 1000;
62
+ class LambdaFlareCommand extends clipanion_1.Command {
63
+ constructor() {
64
+ super(...arguments);
65
+ this.isDryRun = false;
66
+ this.withLogs = false;
67
+ }
68
+ /**
69
+ * Entry point for the `lambda flare` command.
70
+ * Gathers lambda function configuration and sends it to Datadog.
71
+ * @returns 0 if the command ran successfully, 1 otherwise.
72
+ */
73
+ execute() {
74
+ var _a, _b, _c;
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ this.context.stdout.write(flareRenderer.renderLambdaFlareHeader(this.isDryRun));
77
+ // Validate function name
78
+ if (this.functionName === undefined) {
79
+ this.context.stderr.write(commonRenderer.renderError('No function name specified. [-f,--function]'));
80
+ return 1;
81
+ }
82
+ const errorMessages = [];
83
+ // Validate region
84
+ const region = (_b = (_a = 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];
85
+ if (region === undefined) {
86
+ errorMessages.push(commonRenderer.renderNoDefaultRegionSpecifiedError());
87
+ }
88
+ // Validate Datadog API key
89
+ this.apiKey = (_c = process.env[constants_2.CI_API_KEY_ENV_VAR]) !== null && _c !== void 0 ? _c : process.env[constants_2.API_KEY_ENV_VAR];
90
+ if (this.apiKey === undefined) {
91
+ errorMessages.push(commonRenderer.renderError('No Datadog API key specified. Set an API key with the DATADOG_API_KEY environment variable.'));
92
+ }
93
+ if (!this.isDryRun) {
94
+ // Validate case ID
95
+ if (this.caseId === undefined) {
96
+ errorMessages.push(commonRenderer.renderError('No case ID specified. [-c,--case-id]'));
97
+ }
98
+ // Validate email
99
+ if (this.email === undefined) {
100
+ errorMessages.push(commonRenderer.renderError('No email specified. [-e,--email]'));
101
+ }
102
+ }
103
+ // Validate start/end flags if both are specified
104
+ let startMillis;
105
+ let endMillis;
106
+ try {
107
+ ;
108
+ [startMillis, endMillis] = exports.validateStartEndFlags(this.start, this.end);
109
+ }
110
+ catch (err) {
111
+ if (err instanceof Error) {
112
+ errorMessages.push(commonRenderer.renderError(err.message));
113
+ }
114
+ }
115
+ if (errorMessages.length > 0) {
116
+ for (const message of errorMessages) {
117
+ this.context.stderr.write(message);
118
+ }
119
+ return 1;
120
+ }
121
+ // Get AWS credentials
122
+ this.context.stdout.write('\n🔑 Getting AWS credentials...\n');
123
+ try {
124
+ this.credentials = yield commons_1.getAWSCredentials();
125
+ }
126
+ catch (err) {
127
+ if (err instanceof Error) {
128
+ this.context.stderr.write(commonRenderer.renderError(err.message));
129
+ }
130
+ return 1;
131
+ }
132
+ if (this.credentials === undefined) {
133
+ this.context.stdout.write('\n' + commonRenderer.renderNoAWSCredentialsFound());
134
+ try {
135
+ yield prompt_1.requestAWSCredentials();
136
+ }
137
+ catch (err) {
138
+ if (err instanceof Error) {
139
+ this.context.stderr.write(commonRenderer.renderError(err.message));
140
+ }
141
+ return 1;
142
+ }
143
+ }
144
+ // Get and print Lambda function configuration
145
+ this.context.stdout.write('\n🔍 Fetching Lambda function configuration...\n');
146
+ const lambdaClientConfig = {
147
+ region,
148
+ credentials: this.credentials,
149
+ };
150
+ const lambdaClient = new client_lambda_1.LambdaClient(lambdaClientConfig);
151
+ let config;
152
+ try {
153
+ config = yield commons_1.getLambdaFunctionConfig(lambdaClient, this.functionName);
154
+ }
155
+ catch (err) {
156
+ if (err instanceof Error) {
157
+ this.context.stderr.write(commonRenderer.renderError(`Unable to get Lambda function configuration: ${err.message}`));
158
+ }
159
+ return 1;
160
+ }
161
+ config = exports.maskConfig(config);
162
+ const configStr = util_1.default.inspect(config, false, undefined, true);
163
+ this.context.stdout.write(`\n${configStr}\n`);
164
+ // Get tags
165
+ this.context.stdout.write('\n🏷 Getting Resource Tags...\n');
166
+ let tags;
167
+ try {
168
+ tags = yield exports.getTags(lambdaClient, region, config.FunctionArn);
169
+ }
170
+ catch (err) {
171
+ if (err instanceof Error) {
172
+ this.context.stderr.write(commonRenderer.renderError(err.message));
173
+ }
174
+ return 1;
175
+ }
176
+ const tagsLength = Object.keys(tags).length;
177
+ if (tagsLength === 0) {
178
+ this.context.stdout.write(commonRenderer.renderSoftWarning(`No resource tags were found.`));
179
+ }
180
+ else {
181
+ this.context.stdout.write(`✅ Found ${tagsLength} resource tags.\n`);
182
+ }
183
+ // Get CloudWatch logs
184
+ let logs = new Map();
185
+ if (this.withLogs) {
186
+ this.context.stdout.write('\n☁️ Getting CloudWatch logs...\n');
187
+ try {
188
+ logs = yield exports.getAllLogs(region, this.functionName, startMillis, endMillis);
189
+ }
190
+ catch (err) {
191
+ if (err instanceof Error) {
192
+ this.context.stderr.write(commonRenderer.renderError(err.message));
193
+ }
194
+ return 1;
195
+ }
196
+ }
197
+ try {
198
+ // CloudWatch messages
199
+ if (this.withLogs) {
200
+ let message = '\n✅ Found log streams:\n';
201
+ if (logs.size === 0) {
202
+ message = commonRenderer.renderSoftWarning('No CloudWatch log streams were found. Logs will not be retrieved or sent.');
203
+ }
204
+ this.context.stdout.write(message);
205
+ for (const [logStreamName, logEvents] of logs) {
206
+ let warningMessage = '\n';
207
+ if (logEvents.length === 0) {
208
+ warningMessage = ' - ' + commonRenderer.renderSoftWarning('No log events found in this stream');
209
+ }
210
+ this.context.stdout.write(`• ${logStreamName}${warningMessage}`);
211
+ }
212
+ }
213
+ // Create folders
214
+ this.context.stdout.write('\n💾 Saving files...\n');
215
+ const rootFolderPath = path.join(process.cwd(), FLARE_OUTPUT_DIRECTORY);
216
+ const logsFolderPath = path.join(rootFolderPath, LOGS_DIRECTORY);
217
+ if (fs.existsSync(rootFolderPath)) {
218
+ exports.deleteFolder(rootFolderPath);
219
+ }
220
+ exports.createDirectories(rootFolderPath, logsFolderPath, logs);
221
+ // Write files
222
+ const configFilePath = path.join(rootFolderPath, FUNCTION_CONFIG_FILE_NAME);
223
+ exports.writeFile(configFilePath, JSON.stringify(config, undefined, 2));
224
+ this.context.stdout.write(`• Saved function config to ${configFilePath}\n`);
225
+ if (tagsLength > 0) {
226
+ const tagsFilePath = path.join(rootFolderPath, TAGS_FILE_NAME);
227
+ exports.writeFile(tagsFilePath, JSON.stringify(tags, undefined, 2));
228
+ this.context.stdout.write(`• Saved tags to ${tagsFilePath}\n`);
229
+ }
230
+ for (const [logStreamName, logEvents] of logs) {
231
+ if (logEvents.length === 0) {
232
+ continue;
233
+ }
234
+ const logFilePath = path.join(logsFolderPath, `${logStreamName.split('/').join('-')}.csv`);
235
+ const data = exports.convertToCSV(logEvents);
236
+ exports.writeFile(logFilePath, data);
237
+ this.context.stdout.write(`• Saved logs to ${logFilePath}\n`);
238
+ // Sleep for 1 millisecond so creation times are different
239
+ // This allows the logs to be sorted by creation time by the support team
240
+ yield exports.sleep(1);
241
+ }
242
+ // Exit if dry run
243
+ const outputMsg = `\nℹ️ Your output files are located at: ${rootFolderPath}\n\n`;
244
+ if (this.isDryRun) {
245
+ this.context.stdout.write('\n🚫 The flare files were not sent as it was executed in dry run mode.');
246
+ this.context.stdout.write(outputMsg);
247
+ return 0;
248
+ }
249
+ // Confirm before sending
250
+ this.context.stdout.write('\n');
251
+ const answer = yield inquirer_1.default.prompt(prompt_1.confirmationQuestion('Are you sure you want to send the flare file to Datadog Support?'));
252
+ if (!answer.confirmation) {
253
+ this.context.stdout.write('\n🚫 The flare files were not sent based on your selection.');
254
+ this.context.stdout.write(outputMsg);
255
+ return 0;
256
+ }
257
+ // Zip folder
258
+ const zipPath = path.join(rootFolderPath, ZIP_FILE_NAME);
259
+ yield exports.zipContents(rootFolderPath, zipPath);
260
+ // Send to Datadog
261
+ this.context.stdout.write('\n🚀 Sending to Datadog Support...\n');
262
+ yield exports.sendToDatadog(zipPath, this.caseId, this.email, this.apiKey, rootFolderPath);
263
+ this.context.stdout.write('\n✅ Successfully sent flare file to Datadog Support!\n');
264
+ // Delete contents
265
+ exports.deleteFolder(rootFolderPath);
266
+ }
267
+ catch (err) {
268
+ if (err instanceof Error) {
269
+ this.context.stderr.write(commonRenderer.renderError(err.message));
270
+ }
271
+ return 1;
272
+ }
273
+ return 0;
274
+ });
275
+ }
276
+ }
277
+ exports.LambdaFlareCommand = LambdaFlareCommand;
278
+ /**
279
+ * Validate the start and end flags and adds error messages if found
280
+ * @param start start time as a string
281
+ * @param end end time as a string
282
+ * @throws error if start or end are not valid numbers
283
+ * @returns [startMillis, endMillis] as numbers or [undefined, undefined] if both are undefined
284
+ */
285
+ const validateStartEndFlags = (start, end) => {
286
+ if (!start && !end) {
287
+ return [undefined, undefined];
288
+ }
289
+ if (!start) {
290
+ throw Error('Start time is required when end time is specified. [--start]');
291
+ }
292
+ if (!end) {
293
+ throw Error('End time is required when start time is specified. [--end]');
294
+ }
295
+ const startMillis = Number(start);
296
+ let endMillis = Number(end);
297
+ if (isNaN(startMillis)) {
298
+ throw Error(`Start time must be a time in milliseconds since Unix Epoch. '${start}' is not a number.`);
299
+ }
300
+ if (isNaN(endMillis)) {
301
+ throw Error(`End time must be a time in milliseconds since Unix Epoch. '${end}' is not a number.`);
302
+ }
303
+ // Required for AWS SDK to work correctly
304
+ endMillis = Math.min(endMillis, Date.now());
305
+ if (startMillis >= endMillis) {
306
+ throw Error('Start time must be before end time.');
307
+ }
308
+ return [startMillis, endMillis];
309
+ };
310
+ exports.validateStartEndFlags = validateStartEndFlags;
311
+ /**
312
+ * Mask the environment variables in a Lambda function configuration
313
+ * @param config
314
+ */
315
+ const maskConfig = (config) => {
316
+ var _a;
317
+ const environmentVariables = (_a = config.Environment) === null || _a === void 0 ? void 0 : _a.Variables;
318
+ if (!environmentVariables) {
319
+ return config;
320
+ }
321
+ const replacer = commons_1.maskStringifiedEnvVar(environmentVariables);
322
+ const stringifiedConfig = JSON.stringify(config, replacer);
323
+ return JSON.parse(stringifiedConfig);
324
+ };
325
+ exports.maskConfig = maskConfig;
326
+ /**
327
+ * Delete a folder and all its contents
328
+ * @param folderPath the folder to delete
329
+ * @throws Error if the deletion fails
330
+ */
331
+ const deleteFolder = (folderPath) => {
332
+ try {
333
+ fs.rmSync(folderPath, { recursive: true, force: true });
334
+ }
335
+ catch (err) {
336
+ if (err instanceof Error) {
337
+ throw Error(`Failed to delete files located at ${folderPath}: ${err.message}`);
338
+ }
339
+ }
340
+ };
341
+ exports.deleteFolder = deleteFolder;
342
+ /**
343
+ * Creates the root folder and the logs sub-folder
344
+ * @param rootFolderPath path to the root folder
345
+ * @param logsFolderPath path to the logs folder
346
+ * @param logs array of logs
347
+ * @throws Error if the root folder cannot be deleted or folders cannot be created
348
+ */
349
+ const createDirectories = (rootFolderPath, logsFolderPath, logs) => {
350
+ try {
351
+ fs.mkdirSync(rootFolderPath);
352
+ if (logs.size > 0) {
353
+ fs.mkdirSync(logsFolderPath);
354
+ }
355
+ }
356
+ catch (err) {
357
+ if (err instanceof Error) {
358
+ throw Error(`Unable to create directories: ${err.message}`);
359
+ }
360
+ }
361
+ };
362
+ exports.createDirectories = createDirectories;
363
+ /**
364
+ * Gets the LOG_STREAM_COUNT latest log stream names, sorted by last event time
365
+ * @param cwlClient CloudWatch Logs client
366
+ * @param logGroupName name of the log group
367
+ * @param startMillis start time in milliseconds or undefined if no start time is specified
368
+ * @param endMillis end time in milliseconds or undefined if no end time is specified
369
+ * @returns an array of the last LOG_STREAM_COUNT log stream names or an empty array if no log streams are found
370
+ * @throws Error if the log streams cannot be retrieved
371
+ */
372
+ const getLogStreamNames = (cwlClient, logGroupName, startMillis, endMillis) => __awaiter(void 0, void 0, void 0, function* () {
373
+ const config = {
374
+ logGroupName,
375
+ descending: true,
376
+ orderBy: client_cloudwatch_logs_1.OrderBy.LastEventTime,
377
+ limit: DEFAULT_LOG_STREAMS,
378
+ };
379
+ const rangeSpecified = startMillis !== undefined && endMillis !== undefined;
380
+ if (rangeSpecified) {
381
+ config.limit = MAX_LOG_STREAMS;
382
+ }
383
+ const command = new client_cloudwatch_logs_1.DescribeLogStreamsCommand(config);
384
+ const response = yield cwlClient.send(command);
385
+ const logStreams = response.logStreams;
386
+ if (logStreams === undefined || logStreams.length === 0) {
387
+ return [];
388
+ }
389
+ const output = [];
390
+ for (const logStream of logStreams) {
391
+ const logStreamName = logStream.logStreamName;
392
+ if (!logStreamName) {
393
+ continue;
394
+ }
395
+ if (rangeSpecified) {
396
+ const firstEventTime = logStream.firstEventTimestamp;
397
+ const lastEventTime = logStream.lastEventTimestamp;
398
+ if (lastEventTime && lastEventTime < startMillis) {
399
+ continue;
400
+ }
401
+ if (firstEventTime && firstEventTime > endMillis) {
402
+ continue;
403
+ }
404
+ }
405
+ output.push(logStreamName);
406
+ }
407
+ // Reverse array so the oldest log is created first, so Support Staff can sort by creation time
408
+ return output.reverse();
409
+ });
410
+ exports.getLogStreamNames = getLogStreamNames;
411
+ /**
412
+ * Gets the log events for a log stream
413
+ * @param cwlClient
414
+ * @param logGroupName
415
+ * @param logStreamName
416
+ * @param startMillis
417
+ * @param endMillis
418
+ * @returns the log events or an empty array if no log events are found
419
+ * @throws Error if the log events cannot be retrieved
420
+ */
421
+ const getLogEvents = (cwlClient, logGroupName, logStreamName, startMillis, endMillis) => __awaiter(void 0, void 0, void 0, function* () {
422
+ const config = {
423
+ logGroupName,
424
+ logStreamName,
425
+ limit: MAX_LOG_EVENTS_PER_STREAM,
426
+ };
427
+ if (startMillis !== undefined && endMillis !== undefined) {
428
+ config.startTime = startMillis;
429
+ config.endTime = endMillis;
430
+ }
431
+ const command = new client_cloudwatch_logs_1.GetLogEventsCommand(config);
432
+ const response = yield cwlClient.send(command);
433
+ const logEvents = response.events;
434
+ if (logEvents === undefined) {
435
+ return [];
436
+ }
437
+ return logEvents;
438
+ });
439
+ exports.getLogEvents = getLogEvents;
440
+ /**
441
+ * Gets all CloudWatch logs for a function
442
+ * @param region
443
+ * @param functionName
444
+ * @param startMillis start time in milliseconds or undefined if no end time is specified
445
+ * @param endMillis end time in milliseconds or undefined if no end time is specified
446
+ * @returns a map of log stream names to log events or an empty map if no logs are found
447
+ */
448
+ const getAllLogs = (region, functionName, startMillis, endMillis) => __awaiter(void 0, void 0, void 0, function* () {
449
+ const logs = new Map();
450
+ const cwlClient = new client_cloudwatch_logs_1.CloudWatchLogsClient({ region });
451
+ if (functionName.startsWith('arn:aws')) {
452
+ functionName = functionName.split(':')[6];
453
+ }
454
+ const logGroupName = `/aws/lambda/${functionName}`;
455
+ let logStreamNames;
456
+ try {
457
+ logStreamNames = yield exports.getLogStreamNames(cwlClient, logGroupName, startMillis, endMillis);
458
+ }
459
+ catch (err) {
460
+ const msg = err instanceof Error ? err.message : '';
461
+ throw new Error(`Unable to get log streams: ${msg}`);
462
+ }
463
+ for (const logStreamName of logStreamNames) {
464
+ let logEvents;
465
+ try {
466
+ logEvents = yield exports.getLogEvents(cwlClient, logGroupName, logStreamName, startMillis, endMillis);
467
+ }
468
+ catch (err) {
469
+ const msg = err instanceof Error ? err.message : '';
470
+ throw new Error(`Unable to get log events for stream ${logStreamName}: ${msg}`);
471
+ }
472
+ logs.set(logStreamName, logEvents);
473
+ }
474
+ return logs;
475
+ });
476
+ exports.getAllLogs = getAllLogs;
477
+ /**
478
+ * Gets the tags for a function
479
+ * @param lambdaClient
480
+ * @param region
481
+ * @param arn
482
+ * @returns the tags or an empty object if no tags are found
483
+ * @throws Error if the tags cannot be retrieved
484
+ */
485
+ const getTags = (lambdaClient, region, arn) => __awaiter(void 0, void 0, void 0, function* () {
486
+ var _a;
487
+ if (!arn.startsWith('arn:aws')) {
488
+ throw Error(`Invalid function ARN: ${arn}`);
489
+ }
490
+ const command = new client_lambda_1.ListTagsCommand({
491
+ Resource: arn,
492
+ });
493
+ try {
494
+ const response = yield lambdaClient.send(command);
495
+ return (_a = response.Tags) !== null && _a !== void 0 ? _a : {};
496
+ }
497
+ catch (err) {
498
+ let message = '';
499
+ if (err instanceof Error) {
500
+ message = err.message;
501
+ }
502
+ throw Error(`Unable to get resource tags: ${message}`);
503
+ }
504
+ });
505
+ exports.getTags = getTags;
506
+ /**
507
+ * Write the function config to a file
508
+ * @param filePath path to the file
509
+ * @param data the data to write
510
+ * @throws Error if the file cannot be written
511
+ */
512
+ const writeFile = (filePath, data) => {
513
+ try {
514
+ fs.writeFileSync(filePath, data);
515
+ }
516
+ catch (err) {
517
+ if (err instanceof Error) {
518
+ throw Error(`Unable to create function configuration file: ${err.message}`);
519
+ }
520
+ }
521
+ };
522
+ exports.writeFile = writeFile;
523
+ /**
524
+ * Convert the log events to a CSV string
525
+ * @param logEvents array of log events
526
+ * @returns the CSV string
527
+ */
528
+ const convertToCSV = (logEvents) => {
529
+ var _a, _b;
530
+ const rows = [['timestamp', 'datetime', 'message']];
531
+ for (const logEvent of logEvents) {
532
+ const timestamp = `"${(_a = logEvent.timestamp) !== null && _a !== void 0 ? _a : ''}"`;
533
+ let datetime = '';
534
+ if (logEvent.timestamp) {
535
+ const date = new Date(logEvent.timestamp);
536
+ datetime = date.toISOString().replace('T', ' ').replace('Z', '');
537
+ }
538
+ const message = `"${(_b = logEvent.message) !== null && _b !== void 0 ? _b : ''}"`;
539
+ rows.push([timestamp, datetime, message]);
540
+ }
541
+ return rows.join('\n');
542
+ };
543
+ exports.convertToCSV = convertToCSV;
544
+ /**
545
+ * @param ms number of milliseconds to sleep
546
+ */
547
+ const sleep = (ms) => __awaiter(void 0, void 0, void 0, function* () {
548
+ yield new Promise((resolve) => setTimeout(resolve, ms));
549
+ });
550
+ exports.sleep = sleep;
551
+ /**
552
+ * Zip the contents of the flare folder
553
+ * @param rootFolderPath path to the root folder to zip
554
+ * @param zipPath path to save the zip file
555
+ * @throws Error if the zip fails
556
+ */
557
+ const zipContents = (rootFolderPath, zipPath) => __awaiter(void 0, void 0, void 0, function* () {
558
+ const zip = new jszip_1.default();
559
+ const addFolderToZip = (folderPath) => {
560
+ if (!fs.existsSync(folderPath)) {
561
+ throw Error(`Folder does not exist: ${folderPath}`);
562
+ }
563
+ const folder = fs.statSync(folderPath);
564
+ if (!folder.isDirectory()) {
565
+ throw Error(`Path is not a directory: ${folderPath}`);
566
+ }
567
+ const contents = fs.readdirSync(folderPath);
568
+ for (const item of contents) {
569
+ const fullPath = path.join(folderPath, item);
570
+ const file = fs.statSync(fullPath);
571
+ if (file.isDirectory()) {
572
+ addFolderToZip(fullPath);
573
+ }
574
+ else {
575
+ const data = fs.readFileSync(fullPath);
576
+ zip.file(path.relative(rootFolderPath, fullPath), data);
577
+ }
578
+ }
579
+ };
580
+ try {
581
+ addFolderToZip(rootFolderPath);
582
+ const zipContent = yield zip.generateAsync({ type: 'nodebuffer' });
583
+ fs.writeFileSync(zipPath, zipContent);
584
+ }
585
+ catch (err) {
586
+ if (err instanceof Error) {
587
+ throw Error(`Unable to zip the flare files: ${err.message}`);
588
+ }
589
+ }
590
+ });
591
+ exports.zipContents = zipContents;
592
+ /**
593
+ * Calculates the full endpoint URL
594
+ * @throws Error if the site is invalid
595
+ * @returns the full endpoint URL
596
+ */
597
+ const getEndpointUrl = () => {
598
+ var _a, _b;
599
+ const baseUrl = (_b = (_a = process.env[constants_2.CI_SITE_ENV_VAR]) !== null && _a !== void 0 ? _a : process.env[constants_2.SITE_ENV_VAR]) !== null && _b !== void 0 ? _b : constants_1.DATADOG_SITE_US1;
600
+ // The DNS doesn't redirect to the proper endpoint when a subdomain is not present in the baseUrl.
601
+ // There is a DNS inconsistency
602
+ let endpointUrl = baseUrl;
603
+ if ([constants_1.DATADOG_SITE_US1, constants_1.DATADOG_SITE_EU1, constants_1.DATADOG_SITE_GOV].includes(baseUrl)) {
604
+ endpointUrl = 'app.' + baseUrl;
605
+ }
606
+ if (!validation_1.isValidDatadogSite(baseUrl)) {
607
+ throw Error(`Invalid site: ${baseUrl}. Must be one of: ${constants_1.DATADOG_SITES.join(', ')}`);
608
+ }
609
+ return 'https://' + endpointUrl + ENDPOINT_PATH;
610
+ };
611
+ exports.getEndpointUrl = getEndpointUrl;
612
+ /**
613
+ * Send the zip file to Datadog support
614
+ * @param zipPath
615
+ * @param caseId
616
+ * @param email
617
+ * @param apiKey
618
+ * @param rootFolderPath
619
+ * @throws Error if the request fails
620
+ */
621
+ const sendToDatadog = (zipPath, caseId, email, apiKey, rootFolderPath) => __awaiter(void 0, void 0, void 0, function* () {
622
+ var _b, _c, _d;
623
+ const endpointUrl = exports.getEndpointUrl();
624
+ const form = new form_data_1.default();
625
+ form.append('case_id', caseId);
626
+ form.append('flare_file', fs.createReadStream(zipPath));
627
+ form.append('datadog_ci_version', version);
628
+ form.append('email', email);
629
+ const headerConfig = {
630
+ headers: Object.assign(Object.assign({}, form.getHeaders()), { 'DD-API-KEY': apiKey }),
631
+ };
632
+ try {
633
+ yield axios_1.default.post(endpointUrl, form, headerConfig);
634
+ }
635
+ catch (err) {
636
+ // Ensure the root folder is deleted if the request fails
637
+ exports.deleteFolder(rootFolderPath);
638
+ if (axios_1.default.isAxiosError(err)) {
639
+ const errResponse = (_c = (_b = err.response) === null || _b === void 0 ? void 0 : _b.data.error) !== null && _c !== void 0 ? _c : '';
640
+ const errorMessage = (_d = err.message) !== null && _d !== void 0 ? _d : '';
641
+ throw Error(`Failed to send flare file to Datadog Support: ${errorMessage}. ${errResponse}\n`);
642
+ }
643
+ throw err;
644
+ }
645
+ });
646
+ exports.sendToDatadog = sendToDatadog;
647
+ LambdaFlareCommand.addPath('lambda', 'flare');
648
+ LambdaFlareCommand.addOption('isDryRun', clipanion_1.Command.Boolean('-d,--dry'));
649
+ LambdaFlareCommand.addOption('withLogs', clipanion_1.Command.Boolean('--with-logs'));
650
+ LambdaFlareCommand.addOption('functionName', clipanion_1.Command.String('-f,--function'));
651
+ LambdaFlareCommand.addOption('region', clipanion_1.Command.String('-r,--region'));
652
+ LambdaFlareCommand.addOption('caseId', clipanion_1.Command.String('-c,--case-id'));
653
+ LambdaFlareCommand.addOption('email', clipanion_1.Command.String('-e,--email'));
654
+ LambdaFlareCommand.addOption('start', clipanion_1.Command.String('--start'));
655
+ LambdaFlareCommand.addOption('end', clipanion_1.Command.String('--end'));
656
+ //# sourceMappingURL=flare.js.map