@aikidosec/ci-api-client 1.0.17 → 1.0.18
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/README.md +2 -1
- package/lib/aikidoApi.d.ts +16 -0
- package/lib/commands/scan.js +1 -1
- package/lib/commands/scanRelease.d.ts +1 -0
- package/lib/commands/scanRelease.js +21 -3
- package/lib/index.js +1 -1
- package/lib/output.d.ts +1 -0
- package/lib/output.js +3 -0
- package/npm-shrinkwrap.json +7 -9
- package/package.json +2 -2
- package/src/aikidoApi.ts +18 -0
- package/src/commands/scan.ts +1 -1
- package/src/commands/scanRelease.ts +33 -2
- package/src/index.ts +1 -1
- package/src/output.ts +7 -0
package/README.md
CHANGED
|
@@ -49,6 +49,7 @@ Options:
|
|
|
49
49
|
"HIGH", "CRITICAL")
|
|
50
50
|
--poll-interval [interval] The poll interval when checking for an updated scan result (preset: 10)
|
|
51
51
|
--sla-mode Let Aikido only fail only on open issues that have gone out of SLA.
|
|
52
|
+
--json Output the scan result as JSON (gate_passed and issue_breakdown)
|
|
52
53
|
-h, --help display help for command
|
|
53
54
|
```
|
|
54
55
|
|
|
@@ -91,7 +92,7 @@ Options:
|
|
|
91
92
|
|
|
92
93
|
The CLI will spawn a cloud-based scan and then report scan progress. It will exit with exitCode `0` if the scan was successfull (`gate_passed: true`). The process will exit with exitCode `10` if the scan was unsuccesfull (`gate_passed: false`). If anything else goes wrong (e.g. API unavailable, scanning unavailable, other unexpected issue) the process will exit with exitCode `1`.
|
|
93
94
|
|
|
94
|
-
If you want the scan to run quietly (without output), you can add the `--quiet` option to the command.
|
|
95
|
+
If you want the scan to run quietly (without output), you can add the `--quiet` option to the command. If you want machine-readable output instead, add `--json` to `scan-release`. That prints a JSON object with `gate_passed` and `issue_breakdown` and skips spinners and other extra info.
|
|
95
96
|
|
|
96
97
|
Please note that the repository_id which you need to provide to the CLI is the unique ID of the Git provider you are using, not the ID of the repository in Aikido. You can also find this ID in Aikido, by going to the repository's detail page and clicking on the Git provider's icon in the header. Alternatively, you can pass the repository name as it is defined in Aikido.
|
|
97
98
|
|
package/lib/aikidoApi.d.ts
CHANGED
|
@@ -30,8 +30,24 @@ export type TPollIsScanningResult = {
|
|
|
30
30
|
secrets_scan_completed: boolean;
|
|
31
31
|
sbom_scan_completed?: boolean;
|
|
32
32
|
};
|
|
33
|
+
export type TIssueSeverityBreakdown = {
|
|
34
|
+
critical: number;
|
|
35
|
+
high: number;
|
|
36
|
+
medium: number;
|
|
37
|
+
low: number;
|
|
38
|
+
};
|
|
39
|
+
export type TIssueBreakdown = {
|
|
40
|
+
sast: TIssueSeverityBreakdown;
|
|
41
|
+
sca: TIssueSeverityBreakdown;
|
|
42
|
+
iac: TIssueSeverityBreakdown;
|
|
43
|
+
secrets: TIssueSeverityBreakdown;
|
|
44
|
+
malware: TIssueSeverityBreakdown;
|
|
45
|
+
license: TIssueSeverityBreakdown;
|
|
46
|
+
code_quality: TIssueSeverityBreakdown;
|
|
47
|
+
};
|
|
33
48
|
type TPollScanCompletedOptions = {
|
|
34
49
|
gate_passed: boolean;
|
|
50
|
+
issue_breakdown?: TIssueBreakdown;
|
|
35
51
|
};
|
|
36
52
|
type TPollScanFeatureBranchCompletedOptions = {
|
|
37
53
|
new_issues_found: number;
|
package/lib/commands/scan.js
CHANGED
|
@@ -118,7 +118,7 @@ export const scan = async ({ repoId, baseCommitId, headCommitId, branchName, opt
|
|
|
118
118
|
pollStatus();
|
|
119
119
|
};
|
|
120
120
|
const parseCliOptions = (userCliOptions) => {
|
|
121
|
-
const apiOptions = { version: '1.0.
|
|
121
|
+
const apiOptions = { version: '1.0.18' };
|
|
122
122
|
const cliOptions = { pollInterval: 5 };
|
|
123
123
|
if (userCliOptions.pullRequestTitle) {
|
|
124
124
|
apiOptions.pull_request_metadata = {
|
|
@@ -25,6 +25,7 @@ type TScanUserCliOptions = {
|
|
|
25
25
|
pollInterval?: number;
|
|
26
26
|
baseBranch?: string;
|
|
27
27
|
slaMode?: boolean;
|
|
28
|
+
json?: boolean;
|
|
28
29
|
};
|
|
29
30
|
declare function cli(repoId: string, commitId: string, options: TScanUserCliOptions, command: string): Promise<void>;
|
|
30
31
|
export declare const scan: ({ repoId, commitId, options, pollInterval, onStart, onStartComplete, onStartFail, onScanStart, onNextPoll, onScanComplete, onScanFail, }: TScanArguments) => Promise<void>;
|
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { Argument, InvalidArgumentError, Option } from 'commander';
|
|
3
3
|
import { pollScanStatus, startScan, } from '../aikidoApi.js';
|
|
4
4
|
import { getApiKey } from '../configuration.js';
|
|
5
|
-
import { outputError, outputHttpError, outputLog, startSpinner, } from '../output.js';
|
|
5
|
+
import { outputError, outputHttpError, outputJson, outputLog, startSpinner, } from '../output.js';
|
|
6
6
|
async function cli(repoId, commitId, options, command) {
|
|
7
7
|
const apiKey = getApiKey();
|
|
8
8
|
if (!apiKey) {
|
|
@@ -11,6 +11,10 @@ async function cli(repoId, commitId, options, command) {
|
|
|
11
11
|
const { apiOptions, cliOptions } = parseCliOptions(options);
|
|
12
12
|
let loader;
|
|
13
13
|
let pollCount = 1;
|
|
14
|
+
const jsonOutput = cliOptions.json;
|
|
15
|
+
if (jsonOutput) {
|
|
16
|
+
process.env.QUIET = true;
|
|
17
|
+
}
|
|
14
18
|
const onStart = () => {
|
|
15
19
|
loader = startSpinner('Starting Aikido Security release scan');
|
|
16
20
|
};
|
|
@@ -27,6 +31,16 @@ async function cli(repoId, commitId, options, command) {
|
|
|
27
31
|
loader = startSpinner('Waiting for scan to complete');
|
|
28
32
|
};
|
|
29
33
|
const onScanComplete = (pollResult) => {
|
|
34
|
+
if (jsonOutput) {
|
|
35
|
+
outputJson({
|
|
36
|
+
gate_passed: pollResult.gate_passed === true,
|
|
37
|
+
issue_breakdown: pollResult.issue_breakdown,
|
|
38
|
+
});
|
|
39
|
+
if (pollResult.gate_passed !== true) {
|
|
40
|
+
process.exitCode = 10;
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
30
44
|
if (pollResult.gate_passed === true) {
|
|
31
45
|
loader?.succeed('Scan completed, no open issues found');
|
|
32
46
|
if (pollResult.diff_url) {
|
|
@@ -123,8 +137,8 @@ export const scan = async ({ repoId, commitId, options, pollInterval = 10, onSta
|
|
|
123
137
|
pollStatus();
|
|
124
138
|
};
|
|
125
139
|
const parseCliOptions = (userCliOptions) => {
|
|
126
|
-
const apiOptions = { version: '1.0.
|
|
127
|
-
const cliOptions = { pollInterval: 10 };
|
|
140
|
+
const apiOptions = { version: '1.0.18' };
|
|
141
|
+
const cliOptions = { pollInterval: 10, json: false };
|
|
128
142
|
if (userCliOptions.pullRequestTitle) {
|
|
129
143
|
apiOptions.pull_request_metadata = {
|
|
130
144
|
...(apiOptions.pull_request_metadata ?? {}),
|
|
@@ -168,6 +182,9 @@ const parseCliOptions = (userCliOptions) => {
|
|
|
168
182
|
else if (userCliOptions.pollInterval) {
|
|
169
183
|
cliOptions.pollInterval = userCliOptions.pollInterval;
|
|
170
184
|
}
|
|
185
|
+
if (userCliOptions.json) {
|
|
186
|
+
cliOptions.json = true;
|
|
187
|
+
}
|
|
171
188
|
return { apiOptions, cliOptions };
|
|
172
189
|
};
|
|
173
190
|
const validateCommitId = (value) => {
|
|
@@ -193,6 +210,7 @@ export const cliSetup = (program) => program
|
|
|
193
210
|
.argParser(parseFloat))
|
|
194
211
|
.addOption(new Option('--base-branch <branchname>', 'Base branch for the release gated scan.'))
|
|
195
212
|
.option('--sla-mode', 'Let Aikido only fail only on open issues that have gone out of SLA.')
|
|
213
|
+
.option('--json', 'Output the scan result as JSON (gate_passed and issue_breakdown)')
|
|
196
214
|
.description('Run a release scan of an Aikido repo.')
|
|
197
215
|
.action(cli);
|
|
198
216
|
export default { cli, cliSetup, scan };
|
package/lib/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export const program = new Command();
|
|
|
11
11
|
program
|
|
12
12
|
.name('Aikido API Client')
|
|
13
13
|
.description('CLI api client to easily integrate the Aikido public CI API into custom deploy scripts')
|
|
14
|
-
.version('1.0.
|
|
14
|
+
.version('1.0.18');
|
|
15
15
|
apiKey.cliSetup(program);
|
|
16
16
|
scan.cliSetup(program);
|
|
17
17
|
scanRelease.cliSetup(program);
|
package/lib/output.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Ora } from 'ora';
|
|
2
2
|
import { AxiosError } from 'axios';
|
|
3
3
|
export declare const outputLog: (message: string) => void;
|
|
4
|
+
export declare const outputJson: (data: unknown) => void;
|
|
4
5
|
export declare const outputDebug: (variable: any) => void;
|
|
5
6
|
export declare const outputError: (message: string, exitCode?: number) => void;
|
|
6
7
|
export declare const outputHttpError: (axiosError: AxiosError) => void;
|
package/lib/output.js
CHANGED
|
@@ -6,6 +6,9 @@ export const outputLog = (message) => {
|
|
|
6
6
|
console.log(message);
|
|
7
7
|
}
|
|
8
8
|
};
|
|
9
|
+
export const outputJson = (data) => {
|
|
10
|
+
console.log(JSON.stringify(data, null, 2));
|
|
11
|
+
};
|
|
9
12
|
export const outputDebug = (variable) => {
|
|
10
13
|
if (process.env.CLI_DEBUG == true || process.env.CLI_DEBUG == 'true') {
|
|
11
14
|
console.log(chalk.blue(typeof variable == 'object' ? JSON.stringify(variable) : variable));
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikidosec/ci-api-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@aikidosec/ci-api-client",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.18",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"axios": "1.
|
|
12
|
+
"axios": "1.18.0",
|
|
13
13
|
"chalk": "5.3.0",
|
|
14
14
|
"commander": "11.0.0",
|
|
15
15
|
"configstore": "6.0.0",
|
|
@@ -116,8 +116,7 @@
|
|
|
116
116
|
"node_modules/@types/node": {
|
|
117
117
|
"version": "20.3.3",
|
|
118
118
|
"dev": true,
|
|
119
|
-
"license": "MIT"
|
|
120
|
-
"peer": true
|
|
119
|
+
"license": "MIT"
|
|
121
120
|
},
|
|
122
121
|
"node_modules/abbrev": {
|
|
123
122
|
"version": "1.1.1",
|
|
@@ -206,9 +205,9 @@
|
|
|
206
205
|
"license": "MIT"
|
|
207
206
|
},
|
|
208
207
|
"node_modules/axios": {
|
|
209
|
-
"version": "1.
|
|
210
|
-
"resolved": "https://registry.npmjs.org/axios/-/axios-1.
|
|
211
|
-
"integrity": "sha512-
|
|
208
|
+
"version": "1.18.0",
|
|
209
|
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz",
|
|
210
|
+
"integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==",
|
|
212
211
|
"license": "MIT",
|
|
213
212
|
"dependencies": {
|
|
214
213
|
"follow-redirects": "^1.16.0",
|
|
@@ -1271,7 +1270,6 @@
|
|
|
1271
1270
|
"version": "5.1.6",
|
|
1272
1271
|
"dev": true,
|
|
1273
1272
|
"license": "Apache-2.0",
|
|
1274
|
-
"peer": true,
|
|
1275
1273
|
"bin": {
|
|
1276
1274
|
"tsc": "bin/tsc",
|
|
1277
1275
|
"tsserver": "bin/tsserver"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikidosec/ci-api-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "CLI api client to easily integrate the Aikido public CI API into custom deploy scripts",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bert Devriese <bert@builtinbruges.com>",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"axios": "1.
|
|
28
|
+
"axios": "1.18.0",
|
|
29
29
|
"chalk": "5.3.0",
|
|
30
30
|
"commander": "11.0.0",
|
|
31
31
|
"configstore": "6.0.0",
|
package/src/aikidoApi.ts
CHANGED
|
@@ -82,8 +82,26 @@ export type TPollIsScanningResult = {
|
|
|
82
82
|
sbom_scan_completed?: boolean;
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
+
export type TIssueSeverityBreakdown = {
|
|
86
|
+
critical: number;
|
|
87
|
+
high: number;
|
|
88
|
+
medium: number;
|
|
89
|
+
low: number;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export type TIssueBreakdown = {
|
|
93
|
+
sast: TIssueSeverityBreakdown;
|
|
94
|
+
sca: TIssueSeverityBreakdown;
|
|
95
|
+
iac: TIssueSeverityBreakdown;
|
|
96
|
+
secrets: TIssueSeverityBreakdown;
|
|
97
|
+
malware: TIssueSeverityBreakdown;
|
|
98
|
+
license: TIssueSeverityBreakdown;
|
|
99
|
+
code_quality: TIssueSeverityBreakdown;
|
|
100
|
+
};
|
|
101
|
+
|
|
85
102
|
type TPollScanCompletedOptions = {
|
|
86
103
|
gate_passed: boolean;
|
|
104
|
+
issue_breakdown?: TIssueBreakdown;
|
|
87
105
|
};
|
|
88
106
|
|
|
89
107
|
type TPollScanFeatureBranchCompletedOptions = {
|
package/src/commands/scan.ts
CHANGED
|
@@ -245,7 +245,7 @@ export const scan = async ({
|
|
|
245
245
|
const parseCliOptions = (userCliOptions: TScanUserCliOptions) => {
|
|
246
246
|
// Version provided to the API corresponds with the version in package.json
|
|
247
247
|
// of the cli client
|
|
248
|
-
const apiOptions: TScanApiOptions = { version: '1.0.
|
|
248
|
+
const apiOptions: TScanApiOptions = { version: '1.0.18' };
|
|
249
249
|
const cliOptions: TScanCliOptions = { pollInterval: 5 };
|
|
250
250
|
|
|
251
251
|
if (userCliOptions.pullRequestTitle) {
|
|
@@ -14,6 +14,7 @@ import { getApiKey } from '../configuration.js';
|
|
|
14
14
|
import {
|
|
15
15
|
outputError,
|
|
16
16
|
outputHttpError,
|
|
17
|
+
outputJson,
|
|
17
18
|
outputLog,
|
|
18
19
|
startSpinner,
|
|
19
20
|
} from '../output.js';
|
|
@@ -49,6 +50,7 @@ type TScanArguments = {
|
|
|
49
50
|
|
|
50
51
|
type TScanCliOptions = {
|
|
51
52
|
pollInterval: number;
|
|
53
|
+
json: boolean;
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
type TScanUserCliOptions = {
|
|
@@ -63,6 +65,7 @@ type TScanUserCliOptions = {
|
|
|
63
65
|
pollInterval?: number;
|
|
64
66
|
baseBranch?: string;
|
|
65
67
|
slaMode?: boolean;
|
|
68
|
+
json?: boolean;
|
|
66
69
|
};
|
|
67
70
|
|
|
68
71
|
async function cli(
|
|
@@ -83,6 +86,13 @@ async function cli(
|
|
|
83
86
|
let loader: Ora | null;
|
|
84
87
|
let pollCount: number = 1;
|
|
85
88
|
|
|
89
|
+
// when the json option is added, only output the json result.
|
|
90
|
+
// Disabling console output takes care of the spinners and other extra info
|
|
91
|
+
const jsonOutput = cliOptions.json;
|
|
92
|
+
if (jsonOutput) {
|
|
93
|
+
process.env.QUIET = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
86
96
|
// Setup different scan() event handlers
|
|
87
97
|
const onStart = () => {
|
|
88
98
|
loader = startSpinner('Starting Aikido Security release scan');
|
|
@@ -107,6 +117,20 @@ async function cli(
|
|
|
107
117
|
};
|
|
108
118
|
|
|
109
119
|
const onScanComplete = (pollResult: any) => {
|
|
120
|
+
if (jsonOutput) {
|
|
121
|
+
outputJson({
|
|
122
|
+
gate_passed: pollResult.gate_passed === true,
|
|
123
|
+
issue_breakdown: pollResult.issue_breakdown,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (pollResult.gate_passed !== true) {
|
|
127
|
+
// Don't process.exit() here: with piped stdout (CI) the JSON write
|
|
128
|
+
// may still be queued and a forced exit can truncate it.
|
|
129
|
+
process.exitCode = 10;
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
110
134
|
if (pollResult.gate_passed === true) {
|
|
111
135
|
loader?.succeed('Scan completed, no open issues found');
|
|
112
136
|
if (pollResult.diff_url) {
|
|
@@ -245,8 +269,8 @@ export const scan = async ({
|
|
|
245
269
|
const parseCliOptions = (userCliOptions: TScanUserCliOptions) => {
|
|
246
270
|
// Version provided to the API corresponds with the version in package.json
|
|
247
271
|
// of the cli client
|
|
248
|
-
const apiOptions: TScanApiOptions = { version: '1.0.
|
|
249
|
-
const cliOptions: TScanCliOptions = { pollInterval: 10 };
|
|
272
|
+
const apiOptions: TScanApiOptions = { version: '1.0.18' };
|
|
273
|
+
const cliOptions: TScanCliOptions = { pollInterval: 10, json: false };
|
|
250
274
|
|
|
251
275
|
if (userCliOptions.pullRequestTitle) {
|
|
252
276
|
apiOptions.pull_request_metadata = {
|
|
@@ -292,6 +316,9 @@ const parseCliOptions = (userCliOptions: TScanUserCliOptions) => {
|
|
|
292
316
|
} else if (userCliOptions.pollInterval) {
|
|
293
317
|
cliOptions.pollInterval = userCliOptions.pollInterval;
|
|
294
318
|
}
|
|
319
|
+
if (userCliOptions.json) {
|
|
320
|
+
cliOptions.json = true;
|
|
321
|
+
}
|
|
295
322
|
|
|
296
323
|
return { apiOptions, cliOptions };
|
|
297
324
|
};
|
|
@@ -363,6 +390,10 @@ export const cliSetup = (program: Command) =>
|
|
|
363
390
|
'--sla-mode',
|
|
364
391
|
'Let Aikido only fail only on open issues that have gone out of SLA.'
|
|
365
392
|
)
|
|
393
|
+
.option(
|
|
394
|
+
'--json',
|
|
395
|
+
'Output the scan result as JSON (gate_passed and issue_breakdown)'
|
|
396
|
+
)
|
|
366
397
|
.description('Run a release scan of an Aikido repo.')
|
|
367
398
|
.action(cli);
|
|
368
399
|
|
package/src/index.ts
CHANGED
|
@@ -20,7 +20,7 @@ program
|
|
|
20
20
|
.description(
|
|
21
21
|
'CLI api client to easily integrate the Aikido public CI API into custom deploy scripts'
|
|
22
22
|
)
|
|
23
|
-
.version('1.0.
|
|
23
|
+
.version('1.0.18');
|
|
24
24
|
|
|
25
25
|
// Load in all app commands and set them up in the `program` instance
|
|
26
26
|
apiKey.cliSetup(program);
|
package/src/output.ts
CHANGED
|
@@ -10,6 +10,13 @@ export const outputLog = (message: string): void => {
|
|
|
10
10
|
}
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// Output a machine readable result
|
|
14
|
+
// This ignores the global "quiet" option: it is the output that was asked
|
|
15
|
+
// for, not human readable progress information
|
|
16
|
+
export const outputJson = (data: unknown): void => {
|
|
17
|
+
console.log(JSON.stringify(data, null, 2));
|
|
18
|
+
};
|
|
19
|
+
|
|
13
20
|
// Output some message, var, .. if --debug was provided
|
|
14
21
|
export const outputDebug = (variable: any): void => {
|
|
15
22
|
if (process.env.CLI_DEBUG == true || process.env.CLI_DEBUG == 'true') {
|