Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/lib/upload-lib.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
224 lines (224 sloc)
10.4 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
var __importStar = (this && this.__importStar) || function (mod) { | |
if (mod && mod.__esModule) return mod; | |
var result = {}; | |
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | |
result["default"] = mod; | |
return result; | |
}; | |
var __importDefault = (this && this.__importDefault) || function (mod) { | |
return (mod && mod.__esModule) ? mod : { "default": mod }; | |
}; | |
Object.defineProperty(exports, "__esModule", { value: true }); | |
const fs = __importStar(require("fs")); | |
const path = __importStar(require("path")); | |
const zlib_1 = __importDefault(require("zlib")); | |
const core = __importStar(require("@actions/core")); | |
const file_url_1 = __importDefault(require("file-url")); | |
const jsonschema = __importStar(require("jsonschema")); | |
const semver = __importStar(require("semver")); | |
const actionsUtil = __importStar(require("./actions-util")); | |
const api = __importStar(require("./api-client")); | |
const fingerprints = __importStar(require("./fingerprints")); | |
const repository_1 = require("./repository"); | |
const sharedEnv = __importStar(require("./shared-environment")); | |
const util = __importStar(require("./util")); | |
// Takes a list of paths to sarif files and combines them together, | |
// returning the contents of the combined sarif file. | |
function combineSarifFiles(sarifFiles) { | |
const combinedSarif = { | |
version: null, | |
runs: [], | |
}; | |
for (const sarifFile of sarifFiles) { | |
const sarifObject = JSON.parse(fs.readFileSync(sarifFile, "utf8")); | |
// Check SARIF version | |
if (combinedSarif.version === null) { | |
combinedSarif.version = sarifObject.version; | |
} | |
else if (combinedSarif.version !== sarifObject.version) { | |
throw new Error(`Different SARIF versions encountered: ${combinedSarif.version} and ${sarifObject.version}`); | |
} | |
combinedSarif.runs.push(...sarifObject.runs); | |
} | |
return JSON.stringify(combinedSarif); | |
} | |
exports.combineSarifFiles = combineSarifFiles; | |
// Upload the given payload. | |
// If the request fails then this will retry a small number of times. | |
async function uploadPayload(payload, repositoryNwo, apiDetails, mode, logger) { | |
logger.info("Uploading results"); | |
// If in test mode we don't want to upload the results | |
const testMode = process.env["TEST_MODE"] === "true" || false; | |
if (testMode) { | |
return; | |
} | |
const client = api.getApiClient(apiDetails); | |
const reqURL = mode === "actions" | |
? "PUT /repos/:owner/:repo/code-scanning/analysis" | |
: "POST /repos/:owner/:repo/code-scanning/sarifs"; | |
const response = await client.request(reqURL, { | |
owner: repositoryNwo.owner, | |
repo: repositoryNwo.repo, | |
data: payload, | |
}); | |
logger.debug(`response status: ${response.status}`); | |
logger.info("Successfully uploaded results"); | |
} | |
// Recursively walks a directory and returns all SARIF files it finds. | |
// Does not follow symlinks. | |
function findSarifFilesInDir(sarifPath) { | |
const sarifFiles = []; | |
const walkSarifFiles = (dir) => { | |
const entries = fs.readdirSync(dir, { withFileTypes: true }); | |
for (const entry of entries) { | |
if (entry.isFile() && entry.name.endsWith(".sarif")) { | |
sarifFiles.push(path.resolve(dir, entry.name)); | |
} | |
else if (entry.isDirectory()) { | |
walkSarifFiles(path.resolve(dir, entry.name)); | |
} | |
} | |
}; | |
walkSarifFiles(sarifPath); | |
return sarifFiles; | |
} | |
exports.findSarifFilesInDir = findSarifFilesInDir; | |
// Uploads a single sarif file or a directory of sarif files | |
// depending on what the path happens to refer to. | |
// Returns true iff the upload occurred and succeeded | |
async function uploadFromActions(sarifPath, gitHubVersion, apiDetails, logger) { | |
return await uploadFiles(getSarifFilePaths(sarifPath), repository_1.parseRepositoryNwo(actionsUtil.getRequiredEnvParam("GITHUB_REPOSITORY")), await actionsUtil.getCommitOid(), await actionsUtil.getRef(), await actionsUtil.getAnalysisKey(), actionsUtil.getRequiredEnvParam("GITHUB_WORKFLOW"), actionsUtil.getWorkflowRunID(), actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getRequiredInput("matrix"), gitHubVersion, apiDetails, "actions", logger); | |
} | |
exports.uploadFromActions = uploadFromActions; | |
// Uploads a single sarif file or a directory of sarif files | |
// depending on what the path happens to refer to. | |
// Returns true iff the upload occurred and succeeded | |
async function uploadFromRunner(sarifPath, repositoryNwo, commitOid, ref, checkoutPath, gitHubVersion, apiDetails, logger) { | |
return await uploadFiles(getSarifFilePaths(sarifPath), repositoryNwo, commitOid, ref, undefined, undefined, undefined, checkoutPath, undefined, gitHubVersion, apiDetails, "runner", logger); | |
} | |
exports.uploadFromRunner = uploadFromRunner; | |
function getSarifFilePaths(sarifPath) { | |
if (!fs.existsSync(sarifPath)) { | |
throw new Error(`Path does not exist: ${sarifPath}`); | |
} | |
let sarifFiles; | |
if (fs.lstatSync(sarifPath).isDirectory()) { | |
sarifFiles = findSarifFilesInDir(sarifPath); | |
if (sarifFiles.length === 0) { | |
throw new Error(`No SARIF files found to upload in "${sarifPath}".`); | |
} | |
} | |
else { | |
sarifFiles = [sarifPath]; | |
} | |
return sarifFiles; | |
} | |
// Counts the number of results in the given SARIF file | |
function countResultsInSarif(sarif) { | |
let numResults = 0; | |
for (const run of JSON.parse(sarif).runs) { | |
numResults += run.results.length; | |
} | |
return numResults; | |
} | |
exports.countResultsInSarif = countResultsInSarif; | |
// Validates that the given file path refers to a valid SARIF file. | |
// Throws an error if the file is invalid. | |
function validateSarifFileSchema(sarifFilePath, logger) { | |
const sarif = JSON.parse(fs.readFileSync(sarifFilePath, "utf8")); | |
const schema = require("../src/sarif_v2.1.0_schema.json"); | |
const result = new jsonschema.Validator().validate(sarif, schema); | |
if (!result.valid) { | |
// Output the more verbose error messages in groups as these may be very large. | |
for (const error of result.errors) { | |
logger.startGroup(`Error details: ${error.stack}`); | |
logger.info(JSON.stringify(error, null, 2)); | |
logger.endGroup(); | |
} | |
// Set the main error message to the stacks of all the errors. | |
// This should be of a manageable size and may even give enough to fix the error. | |
const sarifErrors = result.errors.map((e) => `- ${e.stack}`); | |
throw new Error(`Unable to upload "${sarifFilePath}" as it is not valid SARIF:\n${sarifErrors.join("\n")}`); | |
} | |
} | |
exports.validateSarifFileSchema = validateSarifFileSchema; | |
// buildPayload constructs a map ready to be uploaded to the API from the given | |
// parameters, respecting the current mode and target GitHub instance version. | |
function buildPayload(commitOid, ref, analysisKey, analysisName, zippedSarif, workflowRunID, checkoutURI, environment, toolNames, gitHubVersion, mode) { | |
if (mode === "actions") { | |
const payloadObj = { | |
commit_oid: commitOid, | |
ref, | |
analysis_key: analysisKey, | |
analysis_name: analysisName, | |
sarif: zippedSarif, | |
workflow_run_id: workflowRunID, | |
checkout_uri: checkoutURI, | |
environment, | |
started_at: process.env[sharedEnv.CODEQL_WORKFLOW_STARTED_AT], | |
tool_names: toolNames, | |
base_ref: undefined, | |
base_sha: undefined, | |
}; | |
// This behaviour can be made the default when support for GHES 3.0 is discontinued. | |
if (gitHubVersion.type !== util.GitHubVariant.GHES || | |
semver.satisfies(gitHubVersion.version, `>=3.1`)) { | |
if (process.env.GITHUB_EVENT_NAME === "pull_request" && | |
process.env.GITHUB_EVENT_PATH) { | |
const githubEvent = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8")); | |
payloadObj.base_ref = `refs/heads/${githubEvent.pull_request.base.ref}`; | |
payloadObj.base_sha = githubEvent.pull_request.base.sha; | |
} | |
} | |
return payloadObj; | |
} | |
else { | |
return { | |
commit_sha: commitOid, | |
ref, | |
sarif: zippedSarif, | |
checkout_uri: checkoutURI, | |
tool_name: toolNames[0], | |
}; | |
} | |
} | |
exports.buildPayload = buildPayload; | |
// Uploads the given set of sarif files. | |
// Returns true iff the upload occurred and succeeded | |
async function uploadFiles(sarifFiles, repositoryNwo, commitOid, ref, analysisKey, analysisName, workflowRunID, checkoutPath, environment, gitHubVersion, apiDetails, mode, logger) { | |
logger.info(`Uploading sarif files: ${JSON.stringify(sarifFiles)}`); | |
if (mode === "actions") { | |
// This check only works on actions as env vars don't persist between calls to the runner | |
const sentinelEnvVar = "CODEQL_UPLOAD_SARIF"; | |
if (process.env[sentinelEnvVar]) { | |
throw new Error("Aborting upload: only one run of the codeql/analyze or codeql/upload-sarif actions is allowed per job"); | |
} | |
core.exportVariable(sentinelEnvVar, sentinelEnvVar); | |
} | |
// Validate that the files we were asked to upload are all valid SARIF files | |
for (const file of sarifFiles) { | |
validateSarifFileSchema(file, logger); | |
} | |
let sarifPayload = combineSarifFiles(sarifFiles); | |
sarifPayload = fingerprints.addFingerprints(sarifPayload, checkoutPath, logger); | |
const zippedSarif = zlib_1.default.gzipSync(sarifPayload).toString("base64"); | |
const checkoutURI = file_url_1.default(checkoutPath); | |
const toolNames = util.getToolNames(sarifPayload); | |
const payload = buildPayload(commitOid, ref, analysisKey, analysisName, zippedSarif, workflowRunID, checkoutURI, environment, toolNames, gitHubVersion, mode); | |
// Log some useful debug info about the info | |
const rawUploadSizeBytes = sarifPayload.length; | |
logger.debug(`Raw upload size: ${rawUploadSizeBytes} bytes`); | |
const zippedUploadSizeBytes = zippedSarif.length; | |
logger.debug(`Base64 zipped upload size: ${zippedUploadSizeBytes} bytes`); | |
const numResultInSarif = countResultsInSarif(sarifPayload); | |
logger.debug(`Number of results in upload: ${numResultInSarif}`); | |
// Make the upload | |
await uploadPayload(payload, repositoryNwo, apiDetails, mode, logger); | |
return { | |
raw_upload_size_bytes: rawUploadSizeBytes, | |
zipped_upload_size_bytes: zippedUploadSizeBytes, | |
num_results_in_sarif: numResultInSarif, | |
}; | |
} | |
//# sourceMappingURL=upload-lib.js.map |