@datadog/datadog-ci 0.18.1 → 1.0.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.
@@ -30,6 +30,7 @@ inquirer,import,MIT,Copyright (c) 2012 Simon Boudrias
30
30
  jest,dev,MIT,Copyright (c) Facebook, Inc. and its affiliates.
31
31
  jest-environment-node,dev,MIT,Copyright (c) Facebook, Inc. and its affiliates.
32
32
  jest-matcher-specific-error,dev,MIT,Copyright (c) 2020 Daniel Hreben
33
+ nexe,dev,MIT,Copyright (c) 2013-2018 Nexe Contributors
33
34
  prettier,dev,MIT,Copyright © James Long and contributors
34
35
  proxy,dev,MIT,Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>
35
36
  proxy-agent,import,MIT,Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>
package/README.md CHANGED
@@ -35,7 +35,6 @@ yarn global add @datadog/datadog-ci
35
35
  Usage: datadog-ci <command> <subcommand> [options]
36
36
 
37
37
  Available commands:
38
- - dependencies
39
38
  - lambda
40
39
  - sourcemaps
41
40
  - synthetics
@@ -49,7 +48,6 @@ Each command allows interacting with a product of the Datadog platform. The comm
49
48
 
50
49
  Further documentation for each command can be found in its folder, ie:
51
50
 
52
- - [Dependencies](src/commands/dependencies)
53
51
  - [Lambda](src/commands/lambda)
54
52
  - [Sourcemaps](src/commands/sourcemaps/)
55
53
  - [Synthetics CI/CD Testing](src/commands/synthetics/)
@@ -8,15 +8,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
11
  Object.defineProperty(exports, "__esModule", { value: true });
15
12
  exports.hasVersionTag = exports.calculateTagRemoveRequest = exports.calculateTagUpdateRequest = exports.applyTagConfig = void 0;
16
- const path_1 = __importDefault(require("path"));
17
13
  const constants_1 = require("./constants");
18
14
  // tslint:disable-next-line
19
- const { version } = require(path_1.default.join(__dirname, '../../../package.json'));
15
+ const { version } = require('../../../package.json');
20
16
  const applyTagConfig = (lambda, config) => __awaiter(void 0, void 0, void 0, function* () {
21
17
  const { tagResourceRequest, untagResourceRequest } = config;
22
18
  if (tagResourceRequest !== undefined) {
File without changes
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const clipanion_1 = require("clipanion");
13
+ class VersionCommand extends clipanion_1.Command {
14
+ execute() {
15
+ return __awaiter(this, void 0, void 0, function* () {
16
+ const { version } = require('../../../package.json');
17
+ this.context.stdout.write(`v${version}\n`);
18
+ return 0;
19
+ });
20
+ }
21
+ }
22
+ VersionCommand.usage = clipanion_1.Command.Usage({
23
+ description: 'Get the current version of datadog-ci.',
24
+ examples: [['Get the current version of datadog-ci', 'datadog-ci version']],
25
+ });
26
+ VersionCommand.addPath('version');
27
+ module.exports = [VersionCommand];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datadog/datadog-ci",
3
- "version": "0.18.1",
3
+ "version": "1.0.0",
4
4
  "description": "Run datadog actions from the CI.",
5
5
  "repository": "https://github.com/DataDog/datadog-ci",
6
6
  "license": "Apache-2.0",
@@ -27,6 +27,8 @@
27
27
  "build": "yarn clean; tsc",
28
28
  "check-licenses": "node bin/check-licenses.js",
29
29
  "clean": "rm -rf dist/*",
30
+ "dist-standalone": "nexe --verbose -i './dist/cli.js' --name datadog-ci --no-fs",
31
+ "dist-standalone:test": "jest --config ./jest.config-standalone.js",
30
32
  "format": "yarn tslint --fix && yarn prettier --write",
31
33
  "launch": "ts-node --transpile-only src/cli.ts",
32
34
  "launch:debug": "node -r ts-node/register/transpile-only --inspect-brk src/cli.ts",
@@ -40,6 +42,7 @@
40
42
  "watch": "tsc -w"
41
43
  },
42
44
  "dependencies": {
45
+ "@types/datadog-metrics": "0.6.1",
43
46
  "async-retry": "1.3.1",
44
47
  "aws-sdk": "2.1012.0",
45
48
  "axios": "0.21.2",
@@ -66,7 +69,6 @@
66
69
  "@babel/preset-env": "7.4.5",
67
70
  "@babel/preset-typescript": "7.3.3",
68
71
  "@types/async-retry": "1.4.2",
69
- "@types/datadog-metrics": "0.6.1",
70
72
  "@types/deep-extend": "0.4.31",
71
73
  "@types/glob": "7.1.1",
72
74
  "@types/inquirer": "8.1.3",
@@ -82,6 +84,7 @@
82
84
  "jest": "27.0.5",
83
85
  "jest-environment-node": "27.0.5",
84
86
  "jest-matcher-specific-error": "1.0.0",
87
+ "nexe": "^4.0.0-beta.19",
85
88
  "prettier": "2.0.5",
86
89
  "proxy": "1.0.2",
87
90
  "ts-jest": "27.0.3",
@@ -1,12 +0,0 @@
1
- /// <reference types="node" />
2
- import { BaseContext } from 'clipanion/lib/advanced';
3
- import { Writable } from 'stream';
4
- interface WritableToString extends Writable {
5
- toString(): string;
6
- }
7
- export interface MockContext extends BaseContext {
8
- stderr: WritableToString;
9
- stdout: WritableToString;
10
- }
11
- export declare const createMockContext: () => MockContext;
12
- export {};
@@ -1,31 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createMockContext = void 0;
4
- const stream_1 = require("stream");
5
- const createMockContext = () => {
6
- const buffer = {
7
- stderr: [],
8
- stdout: [],
9
- };
10
- const stderr = new stream_1.Writable({
11
- write(chunk, encoding, callback) {
12
- buffer.stderr.push(chunk);
13
- callback();
14
- },
15
- });
16
- const stdout = new stream_1.Writable({
17
- write(chunk, encoding, callback) {
18
- buffer.stdout.push(chunk);
19
- callback();
20
- },
21
- });
22
- const stdin = new stream_1.Readable();
23
- Object.assign(stderr, { toString: () => buffer.stderr.join('') });
24
- Object.assign(stdout, { toString: () => buffer.stdout.join('') });
25
- return {
26
- stderr,
27
- stdin,
28
- stdout,
29
- };
30
- };
31
- exports.createMockContext = createMockContext;
@@ -1,2 +0,0 @@
1
- import { Readable } from 'stream';
2
- export declare const streamToString: (stream: Readable) => Promise<string>;
@@ -1,24 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.streamToString = void 0;
4
- const streamToString = (stream) => {
5
- const chunks = [];
6
- return new Promise((resolve, reject) => {
7
- const handleData = (chunk) => chunks.push(chunk);
8
- const handleError = (error) => {
9
- stream.off('data', handleData);
10
- stream.off('end', handleEnd);
11
- reject(error);
12
- };
13
- const handleEnd = () => {
14
- stream.off('data', handleData);
15
- stream.off('error', handleError);
16
- resolve(chunks.join(''));
17
- };
18
- stream.on('data', handleData);
19
- stream.once('error', handleError);
20
- stream.once('end', handleEnd);
21
- stream.resume();
22
- });
23
- };
24
- exports.streamToString = streamToString;
@@ -1,13 +0,0 @@
1
- interface RunUploadInput {
2
- apiKey?: string;
3
- appKey?: string;
4
- dryRun?: boolean;
5
- releaseVersion?: string;
6
- service?: string;
7
- source?: string;
8
- }
9
- export declare const runUploadCommand: (filePath: string, input: RunUploadInput) => Promise<{
10
- context: import("./context").MockContext;
11
- code: number;
12
- }>;
13
- export {};
@@ -1,40 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.runUploadCommand = void 0;
13
- const advanced_1 = require("clipanion/lib/advanced");
14
- const upload_1 = require("../../upload");
15
- const context_1 = require("./context");
16
- const runUploadCommand = (filePath, input) => __awaiter(void 0, void 0, void 0, function* () {
17
- const cli = new advanced_1.Cli();
18
- cli.register(upload_1.UploadCommand);
19
- const context = context_1.createMockContext();
20
- process.env = {
21
- DATADOG_API_KEY: input.apiKey,
22
- DATADOG_APP_KEY: input.appKey,
23
- };
24
- const params = ['dependencies', 'upload', filePath];
25
- if (input.releaseVersion) {
26
- params.push('--release-version', input.releaseVersion);
27
- }
28
- if (input.service) {
29
- params.push('--service', input.service);
30
- }
31
- if (input.source) {
32
- params.push('--source', input.source);
33
- }
34
- if (input.dryRun) {
35
- params.push('--dry-run');
36
- }
37
- const code = yield cli.run(params, context);
38
- return { context, code };
39
- });
40
- exports.runUploadCommand = runUploadCommand;
@@ -1 +0,0 @@
1
- export {};
@@ -1,264 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- const axios_1 = __importDefault(require("axios"));
16
- const chalk_1 = __importDefault(require("chalk"));
17
- const fs_1 = __importDefault(require("fs"));
18
- const path_1 = __importDefault(require("path"));
19
- const stream_1 = require("./helpers/stream");
20
- const upload_run_1 = require("./helpers/upload.run");
21
- describe('execute', () => {
22
- // Disable chalk colors before tests
23
- let previousLevel;
24
- beforeAll(() => {
25
- previousLevel = chalk_1.default.level;
26
- chalk_1.default.level = 0;
27
- });
28
- // Restore chalk colors after tests
29
- afterAll(() => {
30
- chalk_1.default.level = previousLevel;
31
- });
32
- test('runs with --dry-run parameter', () => __awaiter(void 0, void 0, void 0, function* () {
33
- const filePath = './src/commands/dependencies/__tests__/fixtures/dependencies.json';
34
- const resolvedFilePath = path_1.default.resolve(filePath);
35
- const { context, code } = yield upload_run_1.runUploadCommand(filePath, {
36
- apiKey: 'DD_API_KEY_EXAMPLE',
37
- appKey: 'DD_APP_KEY_EXAMPLE',
38
- dryRun: true,
39
- releaseVersion: '1.234',
40
- service: 'my-service',
41
- source: 'snyk',
42
- });
43
- const stdout = context.stdout.toString();
44
- const stderr = context.stderr.toString();
45
- expect(stderr).toEqual('');
46
- expect(stdout).toContain('DRY-RUN MODE ENABLED. WILL NOT UPLOAD DEPENDENCIES.');
47
- expect(stdout).toMatch(new RegExp(`File:[\\s]+${resolvedFilePath}`));
48
- expect(stdout).toMatch(/Source:[\s]+snyk/);
49
- expect(stdout).toMatch(/Service:[\s]+my-service/);
50
- expect(stdout).toMatch(/Version:[\s]+1.234/);
51
- expect(stdout).toContain('[DRYRUN] Uploading dependencies...');
52
- expect(stdout).toMatch(/Dependencies uploaded in .* seconds\./);
53
- expect(code).toBe(0);
54
- }));
55
- test('exits if missing api key', () => __awaiter(void 0, void 0, void 0, function* () {
56
- const { context, code } = yield upload_run_1.runUploadCommand('./src/commands/dependencies/__tests__/fixtures/dependencies.json', {
57
- appKey: 'DD_APP_KEY_EXAMPLE',
58
- dryRun: true,
59
- releaseVersion: '1.234',
60
- service: 'my-service',
61
- source: 'snyk',
62
- });
63
- const stdout = context.stdout.toString();
64
- const stderr = context.stderr.toString();
65
- expect(stderr).toContain('Missing DATADOG_API_KEY in your environment.');
66
- expect(stdout).toEqual('');
67
- expect(code).toBe(1);
68
- }));
69
- test('exits if missing app key', () => __awaiter(void 0, void 0, void 0, function* () {
70
- const { context, code } = yield upload_run_1.runUploadCommand('./src/commands/dependencies/__tests__/fixtures/dependencies.json', {
71
- apiKey: 'DD_API_KEY_EXAMPLE',
72
- dryRun: true,
73
- releaseVersion: '1.234',
74
- service: 'my-service',
75
- source: 'snyk',
76
- });
77
- const stdout = context.stdout.toString();
78
- const stderr = context.stderr.toString();
79
- expect(stderr).toContain('Missing DATADOG_APP_KEY in your environment.');
80
- expect(stdout).toEqual('');
81
- expect(code).toBe(1);
82
- }));
83
- test('exits if missing --service parameter', () => __awaiter(void 0, void 0, void 0, function* () {
84
- const { context, code } = yield upload_run_1.runUploadCommand('./src/commands/dependencies/__tests__/fixtures/dependencies.json', {
85
- apiKey: 'DD_API_KEY_EXAMPLE',
86
- appKey: 'DD_APP_KEY_EXAMPLE',
87
- dryRun: true,
88
- releaseVersion: '1.234',
89
- source: 'snyk',
90
- });
91
- const stdout = context.stdout.toString();
92
- const stderr = context.stderr.toString();
93
- expect(stderr).toContain('Missing --service parameter.');
94
- expect(stdout).toEqual('');
95
- expect(code).toBe(1);
96
- }));
97
- test('exits if missing --source parameter', () => __awaiter(void 0, void 0, void 0, function* () {
98
- const { context, code } = yield upload_run_1.runUploadCommand('./src/commands/dependencies/__tests__/fixtures/dependencies.json', {
99
- apiKey: 'DD_API_KEY_EXAMPLE',
100
- appKey: 'DD_APP_KEY_EXAMPLE',
101
- dryRun: true,
102
- releaseVersion: '1.234',
103
- service: 'my-service',
104
- });
105
- const stdout = context.stdout.toString();
106
- const stderr = context.stderr.toString();
107
- expect(stderr).toContain('Missing --source parameter. Supported values are: snyk');
108
- expect(stdout).toEqual('');
109
- expect(code).toBe(1);
110
- }));
111
- test('exits if invalid --source parameter', () => __awaiter(void 0, void 0, void 0, function* () {
112
- const { context, code } = yield upload_run_1.runUploadCommand('./src/commands/dependencies/__tests__/fixtures/dependencies.json', {
113
- apiKey: 'DD_API_KEY_EXAMPLE',
114
- appKey: 'DD_APP_KEY_EXAMPLE',
115
- dryRun: true,
116
- releaseVersion: '1.234',
117
- service: 'my-service',
118
- source: 'unknown-source',
119
- });
120
- const stdout = context.stdout.toString();
121
- const stderr = context.stderr.toString();
122
- expect(stderr).toContain('Unsupported --source unknown-source. Supported values are: snyk');
123
- expect(stdout).toEqual('');
124
- expect(code).toBe(1);
125
- }));
126
- test("exits if file doesn't exist", () => __awaiter(void 0, void 0, void 0, function* () {
127
- const filePath = './src/commands/dependencies/__tests__/fixtures/unknown-dependencies';
128
- const resolvedFilePath = path_1.default.resolve(filePath);
129
- const { context, code } = yield upload_run_1.runUploadCommand(filePath, {
130
- apiKey: 'DD_API_KEY_EXAMPLE',
131
- appKey: 'DD_APP_KEY_EXAMPLE',
132
- dryRun: true,
133
- releaseVersion: '1.234',
134
- service: 'my-service',
135
- source: 'snyk',
136
- });
137
- const stdout = context.stdout.toString();
138
- const stderr = context.stderr.toString();
139
- expect(stderr).toContain(`Cannot find "${resolvedFilePath}" file.`);
140
- expect(stdout).toEqual('');
141
- expect(code).toBe(2);
142
- }));
143
- test('shows warning if missing --release-version parameter', () => __awaiter(void 0, void 0, void 0, function* () {
144
- const { context, code } = yield upload_run_1.runUploadCommand('./src/commands/dependencies/__tests__/fixtures/dependencies.json', {
145
- apiKey: 'DD_API_KEY_EXAMPLE',
146
- appKey: 'DD_APP_KEY_EXAMPLE',
147
- dryRun: true,
148
- service: 'my-service',
149
- source: 'snyk',
150
- });
151
- const stdout = context.stdout.toString();
152
- const stderr = context.stderr.toString();
153
- expect(stderr).toEqual('');
154
- expect(stdout).toContain('Missing optional --release-version parameter.');
155
- expect(stdout).toContain('The analysis may use out of date dependencies and produce false positives/negatives.');
156
- expect(code).toBe(0);
157
- }));
158
- test('makes a valid API request', () => __awaiter(void 0, void 0, void 0, function* () {
159
- const filePath = './src/commands/dependencies/__tests__/fixtures/dependencies.json';
160
- const resolvedFilePath = path_1.default.resolve(filePath);
161
- const { context, code } = yield upload_run_1.runUploadCommand(filePath, {
162
- apiKey: 'DD_API_KEY_EXAMPLE',
163
- appKey: 'DD_APP_KEY_EXAMPLE',
164
- releaseVersion: '1.234',
165
- service: 'my-service',
166
- source: 'snyk',
167
- });
168
- const stdout = context.stdout.toString();
169
- const stderr = context.stderr.toString();
170
- expect(stderr).toEqual('');
171
- expect(stdout).toMatch(new RegExp(`File:[\\s]+${resolvedFilePath}`));
172
- expect(stdout).toMatch(/Source:[\s]+snyk/);
173
- expect(stdout).toMatch(/Service:[\s]+my-service/);
174
- expect(stdout).toMatch(/Version:[\s]+1.234/);
175
- expect(stdout).toContain('Uploading dependencies...');
176
- expect(stdout).toMatch(/Dependencies uploaded in .* seconds\./);
177
- expect(code).toBe(0);
178
- expect(axios_1.default.post).toHaveBeenCalledWith('https://api.datadoghq.com/profiling/api/v1/dep-graphs', expect.anything(), {
179
- headers: {
180
- 'DD-API-KEY': 'DD_API_KEY_EXAMPLE',
181
- 'DD-APPLICATION-KEY': 'DD_APP_KEY_EXAMPLE',
182
- 'content-type': expect.stringContaining('multipart/form-data'),
183
- },
184
- });
185
- const formData = axios_1.default.post.mock.calls[0][1];
186
- expect(formData).toBeDefined();
187
- // Read stream and normalize EOL
188
- const formPayload = (yield stream_1.streamToString(formData)).replace(/\r\n|\r|\n/g, '\n');
189
- const dependenciesContent = fs_1.default.readFileSync('./src/commands/dependencies/__tests__/fixtures/dependencies.json');
190
- expect(dependenciesContent).not.toBeFalsy();
191
- expect(formPayload).toContain(['Content-Disposition: form-data; name="service"', '', 'my-service'].join('\n'));
192
- expect(formPayload).toContain(['Content-Disposition: form-data; name="version"', '', '1.234'].join('\n'));
193
- expect(formPayload).toContain(['Content-Disposition: form-data; name="source"', '', 'snyk'].join('\n'));
194
- expect(formPayload).toContain([
195
- 'Content-Disposition: form-data; name="file"; filename="dependencies.json"',
196
- 'Content-Type: application/json',
197
- '',
198
- dependenciesContent,
199
- ].join('\n'));
200
- }));
201
- test('handles API errors', () => __awaiter(void 0, void 0, void 0, function* () {
202
- const filePath = './src/commands/dependencies/__tests__/fixtures/dependencies.json';
203
- const resolvedFilePath = path_1.default.resolve(filePath);
204
- axios_1.default.post.mockImplementation(() => Promise.reject(new Error('No access granted')));
205
- const { context, code } = yield upload_run_1.runUploadCommand(filePath, {
206
- apiKey: 'DD_API_KEY_EXAMPLE',
207
- appKey: 'DD_APP_KEY_EXAMPLE',
208
- releaseVersion: '1.234',
209
- service: 'my-service',
210
- source: 'snyk',
211
- });
212
- const stdout = context.stdout.toString();
213
- const stderr = context.stderr.toString();
214
- expect(stderr).toEqual('No access granted\n');
215
- expect(stdout).toMatch(new RegExp(`File:[\\s]+${resolvedFilePath}`));
216
- expect(stdout).toMatch(/Source:[\s]+snyk/);
217
- expect(stdout).toMatch(/Service:[\s]+my-service/);
218
- expect(stdout).toMatch(/Version:[\s]+1.234/);
219
- expect(stdout).toContain('Uploading dependencies...');
220
- expect(stdout).toContain('Failed upload dependencies: No access granted');
221
- expect(code).toBe(3);
222
- }));
223
- test('handles API 403 errors', () => __awaiter(void 0, void 0, void 0, function* () {
224
- ;
225
- axios_1.default.post.mockImplementation(() => Promise.reject({ message: 'Forbidden', isAxiosError: true, response: { status: 403 } }));
226
- const { context, code } = yield upload_run_1.runUploadCommand('./src/commands/dependencies/__tests__/fixtures/dependencies.json', {
227
- apiKey: 'DD_API_KEY_EXAMPLE',
228
- appKey: 'DD_APP_KEY_EXAMPLE',
229
- releaseVersion: '1.234',
230
- service: 'my-service',
231
- source: 'snyk',
232
- });
233
- const stdout = context.stdout.toString();
234
- const stderr = context.stderr.toString();
235
- expect(stderr).toEqual('Forbidden\n');
236
- expect(stdout).toContain('Failed upload dependencies: Forbidden. Check DATADOG_API_KEY and DATADOG_APP_KEY environment variables.');
237
- expect(code).toBe(3);
238
- }));
239
- test('retries on error', () => __awaiter(void 0, void 0, void 0, function* () {
240
- const filePath = './src/commands/dependencies/__tests__/fixtures/dependencies.json';
241
- let didReject = false;
242
- axios_1.default.post.mockImplementation(() => {
243
- if (!didReject) {
244
- didReject = true;
245
- return Promise.reject({ message: 'Internal Server Error', isAxiosError: true, response: { status: 500 } });
246
- }
247
- return Promise.resolve();
248
- });
249
- const { context, code } = yield upload_run_1.runUploadCommand(filePath, {
250
- apiKey: 'DD_API_KEY_EXAMPLE',
251
- appKey: 'DD_APP_KEY_EXAMPLE',
252
- releaseVersion: '1.234',
253
- service: 'my-service',
254
- source: 'snyk',
255
- });
256
- const stdout = context.stdout.toString();
257
- const stderr = context.stderr.toString();
258
- expect(stderr).toEqual('');
259
- expect(stdout).toContain('Uploading dependencies...');
260
- expect(stdout).toContain('[attempt 1] Retrying dependencies upload: Internal Server Error');
261
- expect(stdout).toMatch(/Dependencies uploaded in .* seconds\./);
262
- expect(code).toBe(0);
263
- }));
264
- });
@@ -1,2 +0,0 @@
1
- import { APIHelper } from './interfaces';
2
- export declare const apiConstructor: (baseIntakeUrl: string, apiKey: string, appKey: string) => APIHelper;
@@ -1,27 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.apiConstructor = void 0;
7
- const axios_1 = __importDefault(require("axios"));
8
- const form_data_1 = __importDefault(require("form-data"));
9
- const fs_1 = __importDefault(require("fs"));
10
- const apiConstructor = (baseIntakeUrl, apiKey, appKey) => {
11
- const uploadDependencies = (payload) => {
12
- const form = new form_data_1.default();
13
- form.append('source', payload.source);
14
- form.append('file', fs_1.default.createReadStream(payload.dependenciesFilePath));
15
- form.append('service', payload.service);
16
- if (payload.version) {
17
- form.append('version', payload.version);
18
- }
19
- return axios_1.default.post(`${baseIntakeUrl}/profiling/api/v1/dep-graphs`, form, {
20
- headers: Object.assign({ 'DD-API-KEY': apiKey, 'DD-APPLICATION-KEY': appKey }, form.getHeaders()),
21
- });
22
- };
23
- return {
24
- uploadDependencies,
25
- };
26
- };
27
- exports.apiConstructor = apiConstructor;
@@ -1,4 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const upload_1 = require("./upload");
4
- module.exports = [upload_1.UploadCommand];
@@ -1,10 +0,0 @@
1
- import { AxiosPromise, AxiosResponse } from 'axios';
2
- export interface Payload {
3
- dependenciesFilePath: string;
4
- service: string;
5
- source: string;
6
- version?: string;
7
- }
8
- export interface APIHelper {
9
- uploadDependencies(payload: Payload): AxiosPromise<AxiosResponse>;
10
- }
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,13 +0,0 @@
1
- export declare const renderSupportedValues: (supportedValues: string[]) => string;
2
- export declare const renderMissingParameter: (parameter: string, supportedValues?: string[] | undefined) => string;
3
- export declare const renderMissingEnvironmentVariable: (variable: string) => string;
4
- export declare const renderUnsupportedParameterValue: (parameter: string, value: string, supportedValues: string[]) => string;
5
- export declare const renderMissingReleaseVersionParameter: () => string;
6
- export declare const renderCannotFindFile: (file: string) => string;
7
- export declare const renderCommandInfo: (dependenciesFilePath: string, source: string, service: string, version: string | undefined, dryRun: boolean) => string;
8
- export declare const renderDryRunUpload: () => string;
9
- export declare const renderUpload: () => string;
10
- export declare const renderRetriedUpload: (errorMessage: string, attempt: number) => string;
11
- export declare const renderFailedUploadBecauseOf403: (errorMessage: string) => string;
12
- export declare const renderFailedUpload: (errorMessage: string) => string;
13
- export declare const renderSuccessfulCommand: (duration: number) => string;
@@ -1,57 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.renderSuccessfulCommand = exports.renderFailedUpload = exports.renderFailedUploadBecauseOf403 = exports.renderRetriedUpload = exports.renderUpload = exports.renderDryRunUpload = exports.renderCommandInfo = exports.renderCannotFindFile = exports.renderMissingReleaseVersionParameter = exports.renderUnsupportedParameterValue = exports.renderMissingEnvironmentVariable = exports.renderMissingParameter = exports.renderSupportedValues = void 0;
7
- const chalk_1 = __importDefault(require("chalk"));
8
- const renderSupportedValues = (supportedValues) => `Supported values are: ${chalk_1.default.bold(supportedValues.join('", "'))}`;
9
- exports.renderSupportedValues = renderSupportedValues;
10
- const renderMissingParameter = (parameter, supportedValues) => chalk_1.default.red(`Missing ${chalk_1.default.bold(parameter)} parameter.` +
11
- (supportedValues ? ` ${exports.renderSupportedValues(supportedValues)}\n` : '\n'));
12
- exports.renderMissingParameter = renderMissingParameter;
13
- const renderMissingEnvironmentVariable = (variable) => chalk_1.default.red(`Missing ${chalk_1.default.bold(variable)} in your environment.\n`);
14
- exports.renderMissingEnvironmentVariable = renderMissingEnvironmentVariable;
15
- const renderUnsupportedParameterValue = (parameter, value, supportedValues) => chalk_1.default.red(`Unsupported ${chalk_1.default.bold(parameter)} ${value}. ${exports.renderSupportedValues(supportedValues)}\n`);
16
- exports.renderUnsupportedParameterValue = renderUnsupportedParameterValue;
17
- const renderMissingReleaseVersionParameter = () => {
18
- const releaseVersion = chalk_1.default.bold('--release-version');
19
- return [
20
- chalk_1.default.yellow('┌──────────────────────────────────────────────────────────────────────────────────────┐'),
21
- chalk_1.default.yellow(`│ Missing optional ${releaseVersion} parameter. │`),
22
- chalk_1.default.yellow('│ The analysis may use out of date dependencies and produce false positives/negatives. │'),
23
- chalk_1.default.yellow('└──────────────────────────────────────────────────────────────────────────────────────┘'),
24
- '',
25
- ].join('\n');
26
- };
27
- exports.renderMissingReleaseVersionParameter = renderMissingReleaseVersionParameter;
28
- const renderCannotFindFile = (file) => chalk_1.default.red(`Cannot find "${file}" file.\n`);
29
- exports.renderCannotFindFile = renderCannotFindFile;
30
- const renderCommandInfo = (dependenciesFilePath, source, service, version, dryRun) => {
31
- const lines = [];
32
- if (dryRun) {
33
- lines.push(chalk_1.default.yellow('DRY-RUN MODE ENABLED. WILL NOT UPLOAD DEPENDENCIES.'));
34
- }
35
- lines.push(`${chalk_1.default.bold('File')}: ${dependenciesFilePath}`);
36
- lines.push(`${chalk_1.default.bold('Source')}: ${source}`);
37
- lines.push(`${chalk_1.default.bold('Service')}: ${service}`);
38
- if (version) {
39
- lines.push(`${chalk_1.default.bold('Version')}: ${version}`);
40
- }
41
- lines.push('');
42
- lines.push('');
43
- return lines.join('\n');
44
- };
45
- exports.renderCommandInfo = renderCommandInfo;
46
- const renderDryRunUpload = () => `[DRYRUN] ${exports.renderUpload()}`;
47
- exports.renderDryRunUpload = renderDryRunUpload;
48
- const renderUpload = () => 'Uploading dependencies...\n';
49
- exports.renderUpload = renderUpload;
50
- const renderRetriedUpload = (errorMessage, attempt) => chalk_1.default.yellow(`[attempt ${attempt}] Retrying dependencies upload: ${errorMessage}\n`);
51
- exports.renderRetriedUpload = renderRetriedUpload;
52
- const renderFailedUploadBecauseOf403 = (errorMessage) => exports.renderFailedUpload(`${errorMessage}. Check ${chalk_1.default.bold('DATADOG_API_KEY')} and ${chalk_1.default.bold('DATADOG_APP_KEY')} environment variables.`);
53
- exports.renderFailedUploadBecauseOf403 = renderFailedUploadBecauseOf403;
54
- const renderFailedUpload = (errorMessage) => chalk_1.default.red(`Failed upload dependencies: ${errorMessage}\n`);
55
- exports.renderFailedUpload = renderFailedUpload;
56
- const renderSuccessfulCommand = (duration) => chalk_1.default.green(`Dependencies uploaded in ${duration} seconds.\n`);
57
- exports.renderSuccessfulCommand = renderSuccessfulCommand;
@@ -1,16 +0,0 @@
1
- import { Command } from 'clipanion';
2
- export declare class UploadCommand extends Command {
3
- static SUPPORTED_SOURCES: string[];
4
- static usage: import("clipanion").Usage;
5
- private static INVALID_INPUT_EXIT_CODE;
6
- private static MISSING_FILE_EXIT_CODE;
7
- private static UPLOAD_ERROR_EXIT_CODE;
8
- private config;
9
- private dependenciesFilePath;
10
- private dryRun;
11
- private releaseVersion?;
12
- private service?;
13
- private source?;
14
- execute(): Promise<number | undefined>;
15
- private uploadDependencies;
16
- }
@@ -1,171 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.UploadCommand = void 0;
16
- const async_retry_1 = __importDefault(require("async-retry"));
17
- const clipanion_1 = require("clipanion");
18
- const fs_1 = __importDefault(require("fs"));
19
- const path_1 = __importDefault(require("path"));
20
- const metrics_1 = require("../../helpers/metrics");
21
- const utils_1 = require("../../helpers/utils");
22
- const api_1 = require("./api");
23
- const renderer_1 = require("./renderer");
24
- const errorCodesNoRetry = [400, 403, 413];
25
- class UploadCommand extends clipanion_1.Command {
26
- constructor() {
27
- super(...arguments);
28
- this.config = {
29
- apiHost: utils_1.getApiHostForSite(process.env.DATADOG_SITE || 'datadoghq.com'),
30
- apiKey: process.env.DATADOG_API_KEY,
31
- appKey: process.env.DATADOG_APP_KEY,
32
- };
33
- this.dryRun = false;
34
- }
35
- execute() {
36
- return __awaiter(this, void 0, void 0, function* () {
37
- // Validate input
38
- if (!this.source) {
39
- this.context.stderr.write(renderer_1.renderMissingParameter('--source', UploadCommand.SUPPORTED_SOURCES));
40
- return UploadCommand.INVALID_INPUT_EXIT_CODE;
41
- }
42
- if (UploadCommand.SUPPORTED_SOURCES.indexOf(this.source) === -1) {
43
- this.context.stderr.write(renderer_1.renderUnsupportedParameterValue('--source', this.source, UploadCommand.SUPPORTED_SOURCES));
44
- return UploadCommand.INVALID_INPUT_EXIT_CODE;
45
- }
46
- if (!this.service) {
47
- this.context.stderr.write(renderer_1.renderMissingParameter('--service'));
48
- return UploadCommand.INVALID_INPUT_EXIT_CODE;
49
- }
50
- if (!this.config.appKey) {
51
- this.context.stderr.write(renderer_1.renderMissingEnvironmentVariable('DATADOG_APP_KEY'));
52
- return UploadCommand.INVALID_INPUT_EXIT_CODE;
53
- }
54
- if (!this.config.apiKey) {
55
- this.context.stderr.write(renderer_1.renderMissingEnvironmentVariable('DATADOG_API_KEY'));
56
- return UploadCommand.INVALID_INPUT_EXIT_CODE;
57
- }
58
- // Display warning for missing --release-version
59
- if (!this.releaseVersion) {
60
- this.context.stdout.write(renderer_1.renderMissingReleaseVersionParameter());
61
- }
62
- // Check if file exists (we are not validating the content of the file)
63
- this.dependenciesFilePath = path_1.default.resolve(this.dependenciesFilePath);
64
- if (!fs_1.default.existsSync(this.dependenciesFilePath)) {
65
- this.context.stderr.write(renderer_1.renderCannotFindFile(this.dependenciesFilePath));
66
- return UploadCommand.MISSING_FILE_EXIT_CODE;
67
- }
68
- const defaultTags = [`service:${this.service}`];
69
- if (this.releaseVersion) {
70
- defaultTags.push(`version:${this.releaseVersion}`);
71
- }
72
- const metricsLogger = metrics_1.getMetricsLogger({
73
- datadogSite: process.env.DATADOG_SITE,
74
- defaultTags,
75
- prefix: 'datadog.ci.dependencies.',
76
- });
77
- // Upload dependencies
78
- this.context.stdout.write(renderer_1.renderCommandInfo(this.dependenciesFilePath, this.source, this.service, this.releaseVersion, this.dryRun));
79
- try {
80
- const initialTime = Date.now();
81
- const payload = {
82
- dependenciesFilePath: this.dependenciesFilePath,
83
- service: this.service,
84
- source: this.source,
85
- version: this.releaseVersion,
86
- };
87
- yield this.uploadDependencies(payload, metricsLogger.logger);
88
- const totalTimeSeconds = (Date.now() - initialTime) / 1000;
89
- this.context.stdout.write(renderer_1.renderSuccessfulCommand(totalTimeSeconds));
90
- metricsLogger.logger.gauge('duration', totalTimeSeconds);
91
- }
92
- catch (error) {
93
- this.context.stderr.write(`${error.message}\n`);
94
- return UploadCommand.UPLOAD_ERROR_EXIT_CODE;
95
- }
96
- finally {
97
- try {
98
- yield metricsLogger.flush();
99
- }
100
- catch (err) {
101
- this.context.stdout.write(`WARN: ${err}\n`);
102
- }
103
- }
104
- });
105
- }
106
- uploadDependencies(payload, metricsLogger) {
107
- return __awaiter(this, void 0, void 0, function* () {
108
- const api = api_1.apiConstructor(`https://${this.config.apiHost}`, this.config.apiKey, this.config.appKey);
109
- try {
110
- yield async_retry_1.default((bail) => __awaiter(this, void 0, void 0, function* () {
111
- try {
112
- if (this.dryRun) {
113
- this.context.stdout.write(renderer_1.renderDryRunUpload());
114
- return;
115
- }
116
- this.context.stdout.write(renderer_1.renderUpload());
117
- yield api.uploadDependencies(payload);
118
- metricsLogger.increment('success', 1);
119
- }
120
- catch (error) {
121
- if (error.response && !errorCodesNoRetry.includes(error.response.status)) {
122
- // If it's an axios error and a status code that is not excluded from retries,
123
- // throw the error so that upload is retried
124
- throw error;
125
- }
126
- // If it's another error or an axios error we don't want to retry, bail
127
- bail(error);
128
- return;
129
- }
130
- }), {
131
- onRetry: (error, attempt) => {
132
- metricsLogger.increment('retries', 1);
133
- this.context.stdout.write(renderer_1.renderRetriedUpload(error.message, attempt));
134
- },
135
- retries: 5,
136
- });
137
- }
138
- catch (error) {
139
- if (error.response && error.response.status === 403) {
140
- this.context.stdout.write(renderer_1.renderFailedUploadBecauseOf403(error.message));
141
- }
142
- else {
143
- this.context.stdout.write(renderer_1.renderFailedUpload(error.message));
144
- }
145
- metricsLogger.increment('failed', 1);
146
- throw error;
147
- }
148
- });
149
- }
150
- }
151
- exports.UploadCommand = UploadCommand;
152
- UploadCommand.SUPPORTED_SOURCES = ['snyk'];
153
- UploadCommand.usage = clipanion_1.Command.Usage({
154
- description: 'Upload dependencies graph to Datadog.',
155
- details: 'Uploads dependencies graph to Datadog to detect runtime vulnerabilities by Continuous Profiler. See README for details.',
156
- examples: [
157
- [
158
- 'Upload dependency graph generated by `snyk test --print-deps --sub-project=my-project --json > ./snyk_deps.json` command',
159
- 'datadog-ci dependencies upload ./snyk_deps.json --source snyk --service my-service --release-version 1.234',
160
- ],
161
- ],
162
- });
163
- UploadCommand.INVALID_INPUT_EXIT_CODE = 1;
164
- UploadCommand.MISSING_FILE_EXIT_CODE = 2;
165
- UploadCommand.UPLOAD_ERROR_EXIT_CODE = 3;
166
- UploadCommand.addPath('dependencies', 'upload');
167
- UploadCommand.addOption('dependenciesFilePath', clipanion_1.Command.String({ required: true }));
168
- UploadCommand.addOption('source', clipanion_1.Command.String('--source'));
169
- UploadCommand.addOption('releaseVersion', clipanion_1.Command.String('--release-version'));
170
- UploadCommand.addOption('service', clipanion_1.Command.String('--service'));
171
- UploadCommand.addOption('dryRun', clipanion_1.Command.Boolean('--dry-run'));