1
- const fetch = require ( 'node-fetch' ) ;
2
- const { setFailed, getInput, setOutput } = require ( '@actions/core' ) ;
1
+ import fetch from 'node-fetch' ;
2
+ import { setFailed , getInput , setOutput } from '@actions/core' ;
3
+
4
+ const sleep = ( ms ) => new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
5
+
6
+ async function makeRequest ( url , options ) {
7
+ try {
8
+ const response = await fetch ( url , options ) ;
9
+ if ( ! response . ok ) {
10
+ throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
11
+ }
12
+ return await response . json ( ) ;
13
+ } catch ( error ) {
14
+ console . error ( `Request failed: ${ error . message } ` ) ;
15
+ throw error ;
16
+ }
17
+ }
18
+
19
+ async function pollScanStatus ( apiHost , apiToken , scanId ) {
20
+ let status = 'queued' ;
21
+ let retries = 0 ;
22
+ const maxRetries = 30 ;
23
+ const initialDelay = 10000 ;
24
+
25
+ while ( ( status === 'queued' || status === 'in-progress' ) && retries < maxRetries ) {
26
+ console . log ( `Current scan status: ${ status } . Waiting for completion...` ) ;
27
+
28
+ await sleep ( initialDelay * Math . pow ( 2 , retries ) ) ;
29
+
30
+ const statusData = await makeRequest ( `${ apiHost } /api/v0/scan/${ scanId } /status` , {
31
+ method : 'GET' ,
32
+ headers : { 'Authorization' : `Bearer ${ apiToken } ` }
33
+ } ) ;
34
+
35
+ status = statusData . status ;
36
+ retries ++ ;
37
+
38
+ if ( status === 'completed' ) {
39
+ return statusData . report_url [ 0 ] ;
40
+ }
41
+ }
42
+
43
+ throw new Error ( `Scan did not complete within the expected time. Final status: ${ status } ` ) ;
44
+ }
3
45
4
46
async function run ( ) {
5
47
try {
6
- // Input parameters from the action
7
48
const apiHost = getInput ( 'api_host' ) ;
8
49
const apiToken = getInput ( 'api_token' ) ;
9
50
const imageName = getInput ( 'image_name' ) ;
10
51
const severity = getInput ( 'severity' ) ;
11
52
const publish = getInput ( 'publish' ) ;
12
- const failOnSeverity = getInput ( 'fail_on_severity' ) ; // Get user-defined severity level
53
+ const failOnSeverity = getInput ( 'fail_on_severity' ) ;
13
54
14
- // Step 1: Trigger the scan and get the scan_id
15
- const triggerResponse = await fetch ( `${ apiHost } /api/v0/scan?image=${ imageName } &severity=${ severity } &publish=${ publish } ` , {
55
+ // Validate inputs
56
+ if ( ! apiHost || ! apiToken || ! imageName ) {
57
+ throw new Error ( 'Missing required inputs: api_host, api_token, or image_name' ) ;
58
+ }
59
+
60
+ console . log ( 'Triggering scan...' ) ;
61
+ const triggerData = await makeRequest ( `${ apiHost } /api/v0/scan?image=${ imageName } &severity=${ severity } &publish=${ publish } ` , {
16
62
method : 'POST' ,
17
63
headers : {
18
64
'Authorization' : `Bearer ${ apiToken } ` ,
19
65
'Content-Type' : 'application/json'
20
66
}
21
67
} ) ;
22
68
23
- if ( ! triggerResponse . ok ) {
24
- throw new Error ( `Failed to trigger the scan. Status: ${ triggerResponse . status } ` ) ;
25
- }
26
-
27
- const triggerData = await triggerResponse . json ( ) ;
28
69
const scanId = triggerData . scan_id ;
29
-
30
70
console . log ( `Scan triggered with scan ID: ${ scanId } ` ) ;
31
71
32
- // Step 2: Poll the scan status until it's completed
33
- let status = 'queued' ;
34
- let reportUrl = '' ;
72
+ const reportUrl = await pollScanStatus ( apiHost , apiToken , scanId ) ;
73
+ console . log ( `Scan completed. Report URL: ${ reportUrl } ` ) ;
74
+ setOutput ( 'report_url' , reportUrl ) ;
35
75
36
- while ( status === 'queued' || status === 'in-progress' ) {
37
- console . log ( `Current scan status: ${ status } . Waiting for completion...` ) ;
38
-
39
- await new Promise ( r => setTimeout ( r , 10000 ) ) ; // Wait 10 seconds between polls
40
-
41
- const statusResponse = await fetch ( `${ apiHost } /api/v0/scan/${ scanId } /status` , {
42
- method : 'GET' ,
43
- headers : {
44
- 'Authorization' : `Bearer ${ apiToken } `
45
- }
46
- } ) ;
47
-
48
- if ( ! statusResponse . ok ) {
49
- throw new Error ( `Failed to get scan status. Status: ${ statusResponse . status } ` ) ;
50
- }
51
-
52
- const statusData = await statusResponse . json ( ) ;
53
- status = statusData . status ;
54
-
55
- // Check if scan is completed
56
- if ( status === 'completed' ) {
57
- reportUrl = statusData . report_url [ 0 ] ; // Extract report URL
58
- console . log ( `Scan completed. Report URL: ${ reportUrl } ` ) ;
59
- setOutput ( 'report_url' , reportUrl ) ; // Set the output for future steps
60
- }
61
- }
62
-
63
- // If the scan did not complete successfully
64
- if ( status !== 'completed' ) {
65
- throw new Error ( `Scan failed with status: ${ status } ` ) ;
66
- }
67
-
68
- // Step 3: Check the scan report for vulnerabilities
69
- const reportResponse = await fetch ( reportUrl ) ;
70
- const reportData = await reportResponse . json ( ) ;
76
+ console . log ( 'Fetching scan report...' ) ;
77
+ const reportData = await makeRequest ( reportUrl ) ;
71
78
72
79
if ( failOnSeverity ) {
73
- // Split the severities into an array
74
80
const severitiesToFailOn = failOnSeverity . split ( ',' ) . map ( sev => sev . trim ( ) . toUpperCase ( ) ) ;
75
-
76
- // Check if the report contains any vulnerabilities matching the specified severities
77
81
const hasVulnsToFail = reportData . vulnerabilities . some ( vuln =>
78
82
severitiesToFailOn . includes ( vuln . severity )
79
83
) ;
@@ -86,7 +90,7 @@ async function run() {
86
90
console . log ( 'No fail_on_severity defined, proceeding without failing the job.' ) ;
87
91
}
88
92
} catch ( error ) {
89
- setFailed ( error . message ) ;
93
+ setFailed ( `Action failed: ${ error . message } ` ) ;
90
94
}
91
95
}
92
96
0 commit comments