@@ -8,13 +8,29 @@ import fileUrl from "file-url";
8
8
import * as jsonschema from "jsonschema" ;
9
9
10
10
import * as actionsUtil from "./actions-util" ;
11
+ import {
12
+ getOptionalInput ,
13
+ getRequiredInput ,
14
+ getTemporaryDirectory ,
15
+ } from "./actions-util" ;
11
16
import * as api from "./api-client" ;
17
+ import { getGitHubVersion } from "./api-client" ;
18
+ import { CodeQL , getCodeQL } from "./codeql" ;
19
+ import { getConfig } from "./config-utils" ;
12
20
import { EnvVar } from "./environment" ;
21
+ import { Feature , Features } from "./feature-flags" ;
13
22
import * as fingerprints from "./fingerprints" ;
23
+ import { initCodeQL } from "./init" ;
14
24
import { Logger } from "./logging" ;
15
25
import { parseRepositoryNwo , RepositoryNwo } from "./repository" ;
16
26
import * as util from "./util" ;
17
- import { SarifFile , ConfigurationError , wrapError } from "./util" ;
27
+ import {
28
+ SarifFile ,
29
+ ConfigurationError ,
30
+ wrapError ,
31
+ getRequiredEnvParam ,
32
+ GitHubVersion ,
33
+ } from "./util" ;
18
34
19
35
const GENERIC_403_MSG =
20
36
"The repo on which this action is running has not opted-in to CodeQL code scanning." ;
@@ -48,6 +64,88 @@ function combineSarifFiles(sarifFiles: string[]): SarifFile {
48
64
return combinedSarif ;
49
65
}
50
66
67
+ // Takes a list of paths to sarif files and combines them together using the
68
+ // CLI `github merge-results` command when all SARIF files are produced by
69
+ // CodeQL. Otherwise, it will fall back to combining the files in the action.
70
+ // Returns the contents of the combined sarif file.
71
+ async function combineSarifFilesUsingCLI (
72
+ sarifFiles : string [ ] ,
73
+ gitHubVersion : GitHubVersion ,
74
+ features : Features ,
75
+ logger : Logger ,
76
+ ) : Promise < SarifFile > {
77
+ // First check if all files are produced by CodeQL.
78
+ let allCodeQL = true ;
79
+
80
+ for ( const sarifFile of sarifFiles ) {
81
+ const sarifObject = JSON . parse (
82
+ fs . readFileSync ( sarifFile , "utf8" ) ,
83
+ ) as SarifFile ;
84
+
85
+ const allRunsCodeQL = sarifObject . runs ?. every (
86
+ ( run ) => run . tool ?. driver ?. name === "CodeQL" ,
87
+ ) ;
88
+
89
+ if ( ! allRunsCodeQL ) {
90
+ allCodeQL = false ;
91
+ break ;
92
+ }
93
+ }
94
+
95
+ if ( ! allCodeQL ) {
96
+ logger . warning (
97
+ "Not all SARIF files were produced by CodeQL. Merging files in the action." ,
98
+ ) ;
99
+
100
+ // If not, use the naive method of combining the files.
101
+ return combineSarifFiles ( sarifFiles ) ;
102
+ }
103
+
104
+ // Initialize CodeQL, either by using the config file from the 'init' step,
105
+ // or by initializing it here.
106
+ let codeQL : CodeQL ;
107
+ let tempDir : string ;
108
+
109
+ const config = await getConfig ( actionsUtil . getTemporaryDirectory ( ) , logger ) ;
110
+ if ( config !== undefined ) {
111
+ codeQL = await getCodeQL ( config . codeQLCmd ) ;
112
+ tempDir = config . tempDir ;
113
+ } else {
114
+ logger . warning (
115
+ "Initializing CodeQL since the 'init' Action was not called before this step." ,
116
+ ) ;
117
+
118
+ const apiDetails = {
119
+ auth : getRequiredInput ( "token" ) ,
120
+ externalRepoAuth : getOptionalInput ( "external-repository-token" ) ,
121
+ url : getRequiredEnvParam ( "GITHUB_SERVER_URL" ) ,
122
+ apiURL : getRequiredEnvParam ( "GITHUB_API_URL" ) ,
123
+ } ;
124
+
125
+ const codeQLDefaultVersionInfo = await features . getDefaultCliVersion (
126
+ gitHubVersion . type ,
127
+ ) ;
128
+
129
+ const initCodeQLResult = await initCodeQL (
130
+ undefined , // There is no tools input on the upload action
131
+ apiDetails ,
132
+ getTemporaryDirectory ( ) ,
133
+ gitHubVersion . type ,
134
+ codeQLDefaultVersionInfo ,
135
+ logger ,
136
+ ) ;
137
+
138
+ codeQL = initCodeQLResult . codeql ;
139
+ tempDir = getTemporaryDirectory ( ) ;
140
+ }
141
+
142
+ const outputFile = path . resolve ( tempDir , "combined-sarif.sarif" ) ;
143
+
144
+ await codeQL . mergeResults ( sarifFiles , outputFile , true ) ;
145
+
146
+ return JSON . parse ( fs . readFileSync ( outputFile , "utf8" ) ) as SarifFile ;
147
+ }
148
+
51
149
// Populates the run.automationDetails.id field using the analysis_key and environment
52
150
// and return an updated sarif file contents.
53
151
export function populateRunAutomationDetails (
@@ -363,12 +461,27 @@ async function uploadFiles(
363
461
logger . startGroup ( "Uploading results" ) ;
364
462
logger . info ( `Processing sarif files: ${ JSON . stringify ( sarifFiles ) } ` ) ;
365
463
464
+ const gitHubVersion = await getGitHubVersion ( ) ;
465
+ const features = new Features (
466
+ gitHubVersion ,
467
+ repositoryNwo ,
468
+ actionsUtil . getTemporaryDirectory ( ) ,
469
+ logger ,
470
+ ) ;
471
+
366
472
// Validate that the files we were asked to upload are all valid SARIF files
367
473
for ( const file of sarifFiles ) {
368
474
validateSarifFileSchema ( file , logger ) ;
369
475
}
370
476
371
- let sarif = combineSarifFiles ( sarifFiles ) ;
477
+ let sarif = ( await features . getValue ( Feature . CliSarifMerge ) )
478
+ ? await combineSarifFilesUsingCLI (
479
+ sarifFiles ,
480
+ gitHubVersion ,
481
+ features ,
482
+ logger ,
483
+ )
484
+ : combineSarifFiles ( sarifFiles ) ;
372
485
sarif = await fingerprints . addFingerprints ( sarif , sourceRoot , logger ) ;
373
486
374
487
sarif = populateRunAutomationDetails (
0 commit comments