@datadog/datadog-ci-plugin-lambda 3.21.0 → 3.21.1
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.
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/flare.d.ts +82 -0
- package/dist/commands/flare.js +636 -0
- package/dist/commands/flare.js.map +1 -0
- package/dist/commands/instrument.d.ts +10 -0
- package/dist/commands/instrument.js +405 -0
- package/dist/commands/instrument.js.map +1 -0
- package/dist/commands/uninstrument.d.ts +8 -0
- package/dist/commands/uninstrument.js +264 -0
- package/dist/commands/uninstrument.js.map +1 -0
- package/dist/constants.d.ts +87 -0
- package/dist/constants.js +159 -0
- package/dist/constants.js.map +1 -0
- package/dist/functions/commons.d.ts +131 -0
- package/dist/functions/commons.js +473 -0
- package/dist/functions/commons.js.map +1 -0
- package/dist/functions/instrument.d.ts +7 -0
- package/dist/functions/instrument.js +271 -0
- package/dist/functions/instrument.js.map +1 -0
- package/dist/functions/uninstrument.d.ts +7 -0
- package/dist/functions/uninstrument.js +156 -0
- package/dist/functions/uninstrument.js.map +1 -0
- package/dist/functions/versionChecker.d.ts +3 -0
- package/dist/functions/versionChecker.js +38 -0
- package/dist/functions/versionChecker.js.map +1 -0
- package/dist/interfaces.d.ts +91 -0
- package/dist/interfaces.js +3 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/loggroup.d.ts +17 -0
- package/dist/loggroup.js +140 -0
- package/dist/loggroup.js.map +1 -0
- package/dist/prompt.d.ts +12 -0
- package/dist/prompt.js +265 -0
- package/dist/prompt.js.map +1 -0
- package/dist/renderers/__mocks__/instrument-uninstrument-renderer.d.ts +2 -0
- package/dist/renderers/__mocks__/instrument-uninstrument-renderer.js +11 -0
- package/dist/renderers/__mocks__/instrument-uninstrument-renderer.js.map +1 -0
- package/dist/renderers/common-renderer.d.ts +16 -0
- package/dist/renderers/common-renderer.js +23 -0
- package/dist/renderers/common-renderer.js.map +1 -0
- package/dist/renderers/instrument-uninstrument-renderer.d.ts +397 -0
- package/dist/renderers/instrument-uninstrument-renderer.js +506 -0
- package/dist/renderers/instrument-uninstrument-renderer.js.map +1 -0
- package/dist/tags.d.ts +8 -0
- package/dist/tags.js +74 -0
- package/dist/tags.js.map +1 -0
- 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
|