@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.
- package/LICENSE-3rdparty.csv +1 -0
- package/README.md +0 -2
- package/dist/commands/lambda/tags.js +1 -5
- package/dist/commands/{dependencies → version}/cli.d.ts +0 -0
- package/dist/commands/version/cli.js +27 -0
- package/package.json +5 -2
- package/dist/commands/dependencies/__tests__/helpers/context.d.ts +0 -12
- package/dist/commands/dependencies/__tests__/helpers/context.js +0 -31
- package/dist/commands/dependencies/__tests__/helpers/stream.d.ts +0 -2
- package/dist/commands/dependencies/__tests__/helpers/stream.js +0 -24
- package/dist/commands/dependencies/__tests__/helpers/upload.run.d.ts +0 -13
- package/dist/commands/dependencies/__tests__/helpers/upload.run.js +0 -40
- package/dist/commands/dependencies/__tests__/upload.test.d.ts +0 -1
- package/dist/commands/dependencies/__tests__/upload.test.js +0 -264
- package/dist/commands/dependencies/api.d.ts +0 -2
- package/dist/commands/dependencies/api.js +0 -27
- package/dist/commands/dependencies/cli.js +0 -4
- package/dist/commands/dependencies/interfaces.d.ts +0 -10
- package/dist/commands/dependencies/interfaces.js +0 -2
- package/dist/commands/dependencies/renderer.d.ts +0 -13
- package/dist/commands/dependencies/renderer.js +0 -57
- package/dist/commands/dependencies/upload.d.ts +0 -16
- package/dist/commands/dependencies/upload.js +0 -171
package/LICENSE-3rdparty.csv
CHANGED
|
@@ -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(
|
|
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.
|
|
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,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,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,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,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'));
|