@@ -29,10 +29,6 @@ const JobStatus = {
2929 FAILED: "failed"
3030}
3131
32- function Synchronously(fn) {
33- return new Promise((resolve) => resolve(fn()))
34- }
35-
3632function commaSeparated(str) {
3733 return (str || '').split(',').map(s => s.trim()).filter(s => s.length > 0)
3834}
@@ -45,115 +41,103 @@ function buildGlobObject(globsArray) {
4541 return glob.create(globsArray.join('\n'))
4642}
4743
48- function prepareInputsZip(inputsGlob, targetFile) {
49- return Synchronously(() => {
50- const separatedInputGlobs = commaSeparated(inputsGlob);
51- core.debug("Got input file globs: " + separatedInputGlobs)
52- if (!areGlobsValid(separatedInputGlobs)) {
53- throw new Error("No globs specified for source/binary input files")
54- }
55- return separatedInputGlobs
56- })
57- .then((globs) => buildGlobObject(globs))
58- .then(async (inputFilesGlob) => {
59- const output = fs.createWriteStream(targetFile);
60- const archive = archiver('zip');
61- archive.on('end', () => console.log("Finished writing ZIP"))
62- archive.on('warning', (err) => console.log("Warning: ", err))
63- archive.on('error', (err) => console.log("Error: ", err))
64-
65- archive.pipe(output);
66-
67- let numWritten = 0
68- for await (const file of inputFilesGlob.globGenerator()) {
69- archive.file(file);
70- numWritten += 1
71- }
72- await archive.finalize()
73- return numWritten
74- })
44+ async function prepareInputsZip(inputsGlob, targetFile) {
45+ const separatedInputGlobs = commaSeparated(inputsGlob);
46+ core.debug("Got input file globs: " + separatedInputGlobs)
47+ if (!areGlobsValid(separatedInputGlobs)) {
48+ throw new Error("No globs specified for source/binary input files")
49+ }
50+
51+ const inputFilesGlob = await buildGlobObject(separatedInputGlobs)
52+ const output = fs.createWriteStream(targetFile);
53+ const archive = archiver('zip');
54+ archive.on('end', () => core.info("Finished writing ZIP"))
55+ archive.on('warning', (err) => core.warning("Warning when writing ZIP: ", err))
56+ archive.on('error', (err) => core.error("Error when writing ZIP: ", err))
57+
58+ archive.pipe(output);
59+
60+ let numWritten = 0
61+ for await (const file of inputFilesGlob.globGenerator()) {
62+ archive.file(file);
63+ numWritten += 1
64+ }
65+ await archive.finalize()
66+ return numWritten
7567}
7668
77- function attachInputsZip(inputGlobs, formData, tmpDir) {
69+ async function attachInputsZip(inputGlobs, formData, tmpDir) {
7870 const zipTarget = path.join(tmpDir, "codedx-inputfiles.zip")
79- return prepareInputsZip(inputGlobs, zipTarget)
80- .then((numFiles) => Synchronously(() => {
81- if (numFiles == 0) {
82- throw new Error("No files were matched by the source/binary glob(s)")
83- } else {
84- core.info(`Added ${numFiles} files`)
85- }
71+ const numFiles = await prepareInputsZip(inputGlobs, zipTarget)
72+ if (numFiles == 0) {
73+ throw new Error("No files were matched by the source/binary glob(s)")
74+ } else {
75+ core.info(`Added ${numFiles} files`)
76+ }
8677
87- formData.append('source-and-binaries.zip', fs.createReadStream(zipTarget))
88- }))
78+ formData.append('source-and-binaries.zip', fs.createReadStream(zipTarget))
8979}
9080
91- function attachScanFiles(scanGlobs, formData) {
81+ async function attachScanFiles(scanGlobs, formData) {
9282 const separatedScanGlobs = commaSeparated(scanGlobs)
9383 core.debug("Got scan file globs: " + separatedScanGlobs)
9484
9585 if (areGlobsValid(separatedScanGlobs)) {
96- return buildGlobObject(separatedScanGlobs)
97- .then(async (scanFilesGlob) => {
98- core.info("Searching with globs...")
99- let numWritten = 0
100- for await (const file of scanFilesGlob.globGenerator()) {
101- numWritten += 1
102- core.info('- Adding ' + file)
103- const name = path.basename(file)
104- formData.append(`${numWritten}-${name}`, fs.createReadStream(file))
105- }
106- core.info(`Found and added ${numWritten} scan files`)
107- })
86+ const scanFilesGlob = await buildGlobObject(separatedScanGlobs)
87+ core.info("Searching with globs...")
88+ let numWritten = 0
89+ for await (const file of scanFilesGlob.globGenerator()) {
90+ numWritten += 1
91+ core.info('- Adding ' + file)
92+ const name = path.basename(file)
93+ formData.append(`${numWritten}-${name}`, fs.createReadStream(file))
94+ }
95+ core.info(`Found and added ${numWritten} scan files`)
10896 } else {
109- return Synchronously(() => core.info("(Scan files skipped as no globs were specified)") )
97+ core.info("(Scan files skipped as no globs were specified)")
11098 }
11199}
112100
113101// most @actions toolkit packages have async methods
114102module.exports = async function run() {
115- try {
116- const config = getConfig()
103+ const config = getConfig()
117104
118- const client = new CodeDxApiClient(config.serverUrl, config.apiKey, config.caCert)
119- core.info("Checking connection to Code Dx...")
105+ const client = new CodeDxApiClient(config.serverUrl, config.apiKey, config.caCert)
106+ core.info("Checking connection to Code Dx...")
120107
121- const codedxVersion = await client.testConnection()
122- core.info("Confirmed - using Code Dx " + codedxVersion)
108+ const codedxVersion = await client.testConnection()
109+ core.info("Confirmed - using Code Dx " + codedxVersion)
123110
124- core.info("Checking API key permissions...")
125- await client.validatePermissions(config.projectId)
126- core.info("Connection to Code Dx server is OK.")
111+ core.info("Checking API key permissions...")
112+ await client.validatePermissions(config.projectId)
113+ core.info("Connection to Code Dx server is OK.")
127114
128- const formData = new FormData()
129-
130- core.info("Preparing source/binaries ZIP...")
131- await attachInputsZip(config.inputGlobs, formData, config.tmpDir)
115+ const formData = new FormData()
116+
117+ core.info("Preparing source/binaries ZIP...")
118+ await attachInputsZip(config.inputGlobs, formData, config.tmpDir)
132119
133- core.info("Adding scan files...")
134- await attachScanFiles(config.scanGlobs, formData)
120+ core.info("Adding scan files...")
121+ await attachScanFiles(config.scanGlobs, formData)
135122
136- core.info("Uploading to Code Dx...")
137- const { analysisId, jobId } = await client.runAnalysis(config.projectId, formData)
123+ core.info("Uploading to Code Dx...")
124+ const { analysisId, jobId } = await client.runAnalysis(config.projectId, formData)
138125
139- core.info("Started analysis #" + analysisId)
126+ core.info("Started analysis #" + analysisId)
140127
141- if (config.waitForCompletion) {
142- core.info("Waiting for job to finish...")
143- let lastStatus = null
144- do {
145- await wait(1000)
146- lastStatus = await client.checkJobStatus(jobId)
147- } while (lastStatus != JobStatus.COMPLETED && lastStatus != JobStatus.FAILED)
128+ if (config.waitForCompletion) {
129+ core.info("Waiting for job to finish...")
130+ let lastStatus = null
131+ do {
132+ await wait(1000)
133+ lastStatus = await client.checkJobStatus(jobId)
134+ } while (lastStatus != JobStatus.COMPLETED && lastStatus != JobStatus.FAILED)
148135
149- if (lastStatus == JobStatus.COMPLETED) {
150- core.info("Analysis finished! Completed with status: " + lastStatus)
151- } else {
152- throw new Error("Analysis finished with non-complete status: " + lastStatus)
153- }
136+ if (lastStatus == JobStatus.COMPLETED) {
137+ core.info("Analysis finished! Completed with status: " + lastStatus)
138+ } else {
139+ throw new Error("Analysis finished with non-complete status: " + lastStatus)
154140 }
155- } catch (ex) {
156- throw ex
157141 }
158142}
159143
@@ -189,6 +173,10 @@ function parseError(e) {
189173 }
190174}
191175
176+ function rethrowError(err) {
177+ throw parseError(err)
178+ }
179+
192180class CodeDxApiClient {
193181 constructor(baseUrl, apiKey, caCert) {
194182 const httpsAgent = caCert ? new https.Agent({ ca: caCert }) : undefined
@@ -211,6 +199,8 @@ class CodeDxApiClient {
211199 })
212200 }
213201
202+ // WARNING: This logging will emit Header data, which contains the Code Dx API key. This should not be exposed and should only
203+ // be used for internal testing.
214204 useLogging() {
215205 this.anonymousHttp.interceptors.request.use(AxiosLogger.requestLogger, AxiosLogger.errorLogger)
216206 this.http.interceptors.request.use(AxiosLogger.requestLogger, AxiosLogger.errorLogger)
@@ -219,58 +209,57 @@ class CodeDxApiClient {
219209 this.http.interceptors.response.use(AxiosLogger.responseLogger, AxiosLogger.errorLogger)
220210 }
221211
222- testConnection() {
223- return this.anonymousHttp.get('/x/system-info')
224- .then(response => new Promise((resolve) => {
225- if (typeof response.data != 'object') {
226- throw new Error(`Expected JSON Object response, got ${typeof response.data}. Is this a Code Dx instance?`)
227- }
228-
229- const expectedFields = ['version', 'date']
230- const unexpectedFields = _.without(_.keys(response.data), ...expectedFields)
231- if (unexpectedFields.length > 0) {
232- throw new Error(`Received unexpected fields ${unexpectedFields.join(', ')}. Is this a Code Dx instance?`)
233- }
212+ async testConnection() {
213+ const response = await this.anonymousHttp.get('/x/system-info').catch(rethrowError)
234214
235- resolve(response.data.version)
236- }))
237- .catch(e => { throw parseError(e) })
215+ if (typeof response.data != 'object') {
216+ throw new Error(`Expected JSON Object response, got ${typeof response.data}. Is this a Code Dx instance?`)
217+ }
218+
219+ const expectedFields = ['version', 'date']
220+ const unexpectedFields = _.without(_.keys(response.data), ...expectedFields)
221+ if (unexpectedFields.length > 0) {
222+ throw new Error(`Received unexpected fields ${unexpectedFields.join(', ')}. Is this a Code Dx instance?`)
223+ }
224+
225+ return response.data.version
238226 }
239227
240- validatePermissions(projectId) {
241- const neededPermissions = [
242- ` analysis:create:${projectId}`
228+ async validatePermissions(projectId) {
229+ const cleanNeededPermissions = [
230+ ' analysis:create'
243231 ]
244232
245- return this.http.post('/x/check-permissions', neededPermissions)
246- .catch(e => {
247- if (axios.isAxiosError(e) && e.response.status == 403) {
248- throw new Error("Permissions check responded with HTTP 403, is the API key valid?")
249- } else {
250- throw e
251- }
233+ const neededPermissions = cleanNeededPermissions.map(p => `${p}:${projectId}`)
234+
235+ const response = await this.http.post('/x/check-permissions', neededPermissions).catch(e => {
236+ if (axios.isAxiosError(e) && e.response.status == 403) {
237+ throw new Error("Permissions check responded with HTTP 403, is the API key valid?")
238+ } else {
239+ throw parseError(e)
240+ }
241+ })
242+
243+ const permissions = response.data
244+ const missingPermissions = neededPermissions.filter(p => !permissions[p])
245+ if (missingPermissions.length > 0) {
246+ const cleanMissingPermissions = missingPermissions.map(p => {
247+ const parts = p.split(':')
248+ return parts.slice(0, -1).join(':')
252249 })
253- .then(response => new Promise(resolve => {
254- const permissions = response.data
255- const missingPermissions = neededPermissions.filter(p => !permissions[p])
256- if (missingPermissions.length > 0) {
257- const summary = missingPermissions.join(', ')
258- throw new Error("The following permissions were missing for the given API Key: " + summary)
259- }
260- resolve()
261- }))
250+ const summary = cleanMissingPermissions.join(', ')
251+ throw new Error("The following permissions were missing for the given API Key: " + summary)
252+ }
262253 }
263254
264- runAnalysis(projectId, formData) {
265- return this.http.post(`/api/projects/${projectId}/analysis`, formData, { headers: formData.getHeaders() })
266- .catch(ex => { throw parseError(ex) })
267- .then(response => new Promise(resolve => resolve(response.data)))
255+ async runAnalysis(projectId, formData) {
256+ const response = await this.http.post(`/api/projects/${projectId}/analysis`, formData, { headers: formData.getHeaders() }).catch(rethrowError)
257+ return response.data
268258 }
269259
270- checkJobStatus(jobId) {
271- return this.http.get('/api/jobs/' + jobId)
272- .catch(ex => { throw parseError(ex) })
273- .then(result => new Promise(resolve => resolve(result.data.status)))
260+ async checkJobStatus(jobId) {
261+ const response = await this.http.get('/api/jobs/' + jobId).catch(rethrowError)
262+ return response.data.status
274263 }
275264}
276265
0 commit comments