mirror of
https://github.com/github/codeql-action.git
synced 2025-12-29 02:30:11 +08:00
Search for the CodeQL bundle in multiple places.
This commit is contained in:
105
src/codeql.ts
105
src/codeql.ts
@@ -1,10 +1,18 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as http from '@actions/http-client';
|
||||
import { IHeaders } from '@actions/http-client/interfaces';
|
||||
import * as io from '@actions/io';
|
||||
import * as toolcache from '@actions/tool-cache';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as semver from 'semver';
|
||||
import * as stream from 'stream';
|
||||
import * as globalutil from 'util';
|
||||
import uuidV4 from 'uuid/v4';
|
||||
|
||||
import * as api from './api-client';
|
||||
import * as util from './util';
|
||||
|
||||
export interface CodeQL {
|
||||
@@ -74,16 +82,107 @@ let cachedCodeQL: CodeQL | undefined = undefined;
|
||||
*/
|
||||
const CODEQL_ACTION_CMD = "CODEQL_ACTION_CMD";
|
||||
|
||||
const CODEQL_DEFAULT_BUNDLE_VERSION = "codeql-bundle-20200630";
|
||||
const CODEQL_DEFAULT_BUNDLE_NAME = "codeql-bundle.tar.gz";
|
||||
const GITHUB_DOTCOM_API_URL = "https://api.github.com";
|
||||
const INSTANCE_API_URL = process.env["GITHUB_API_URL"] || GITHUB_DOTCOM_API_URL;
|
||||
const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
|
||||
|
||||
function getCodeQLActionRepository(): string {
|
||||
// Actions do not know their own repository name,
|
||||
// so we currently use this hack to find the name based on where our files are.
|
||||
// This can be removed once the change to the runner in https://github.com/actions/runner/pull/585 is deployed.
|
||||
const runnerTemp = util.getRequiredEnvParam("RUNNER_TEMP");
|
||||
const actionsDirectory = path.join(path.dirname(runnerTemp), "_actions");
|
||||
const relativeScriptPath = path.relative(actionsDirectory, __filename);
|
||||
if (relativeScriptPath.startsWith("..") || path.isAbsolute(relativeScriptPath)) {
|
||||
return CODEQL_DEFAULT_ACTION_REPOSITORY;
|
||||
}
|
||||
const relativeScriptPathParts = relativeScriptPath.split(path.sep);
|
||||
return relativeScriptPathParts[0] + "/" + relativeScriptPathParts[1];
|
||||
}
|
||||
|
||||
async function getCodeQLBundleDownloadURL(): Promise<string> {
|
||||
const codeQLActionRepository = getCodeQLActionRepository();
|
||||
const potentialDownloadSources = [
|
||||
// This GitHub instance, and this Action.
|
||||
[INSTANCE_API_URL, codeQLActionRepository],
|
||||
// This GitHub instance, and the canonical Action.
|
||||
[INSTANCE_API_URL, CODEQL_DEFAULT_ACTION_REPOSITORY],
|
||||
// GitHub.com, and the canonical Action.
|
||||
[GITHUB_DOTCOM_API_URL, CODEQL_DEFAULT_ACTION_REPOSITORY],
|
||||
];
|
||||
// We now filter out any duplicates.
|
||||
// Duplicates will happen either because the GitHub instance is GitHub.com, or because the Action is not a fork.
|
||||
const uniqueDownloadSources = potentialDownloadSources.filter((url, index, self) => index === self.indexOf(url));
|
||||
for (let downloadSource of uniqueDownloadSources) {
|
||||
let [apiURL, repository] = downloadSource;
|
||||
// If we've reached the final case, short-circuit the API check since we know the bundle exists and is public.
|
||||
if (apiURL === GITHUB_DOTCOM_API_URL && repository === CODEQL_DEFAULT_ACTION_REPOSITORY) {
|
||||
return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${CODEQL_DEFAULT_BUNDLE_VERSION}/${CODEQL_DEFAULT_BUNDLE_NAME}`;
|
||||
}
|
||||
let [repositoryOwner, repositoryName] = repository.split("/");
|
||||
let client = apiURL === INSTANCE_API_URL ? api.getApiClient() : new Octokit();
|
||||
try {
|
||||
const release = await client.repos.getReleaseByTag({
|
||||
owner: repositoryOwner,
|
||||
repo: repositoryName,
|
||||
tag: CODEQL_DEFAULT_BUNDLE_VERSION
|
||||
});
|
||||
for (let asset of release.data.assets) {
|
||||
if (asset.name === CODEQL_DEFAULT_BUNDLE_NAME) {
|
||||
core.info(`Found CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} with URL ${asset.url}.`);
|
||||
return asset.url;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
core.info(`Looked for CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} but got error ${e}.`);
|
||||
}
|
||||
}
|
||||
throw new Error("Could not find an accessible CodeQL bundle.");
|
||||
}
|
||||
|
||||
export async function setupCodeQL(): Promise<CodeQL> {
|
||||
try {
|
||||
const codeqlURL = core.getInput('tools', { required: true });
|
||||
const codeqlURLVersion = getCodeQLURLVersion(codeqlURL);
|
||||
let codeqlURL = core.getInput('tools');
|
||||
const codeqlURLVersion = getCodeQLURLVersion(codeqlURL || `/${CODEQL_DEFAULT_BUNDLE_VERSION}/`);
|
||||
|
||||
let codeqlFolder = toolcache.find('CodeQL', codeqlURLVersion);
|
||||
if (codeqlFolder) {
|
||||
core.debug(`CodeQL found in cache ${codeqlFolder}`);
|
||||
} else {
|
||||
const codeqlPath = await toolcache.downloadTool(codeqlURL);
|
||||
if (!codeqlURL) {
|
||||
codeqlURL = await getCodeQLBundleDownloadURL();
|
||||
}
|
||||
|
||||
// We have to download CodeQL manually because the toolcache doesn't support Accept headers.
|
||||
// This can be changed to a one-liner once https://github.com/actions/toolkit/pull/530 is merged and released.
|
||||
const client = new http.HttpClient('CodeQL Action');
|
||||
const codeqlPath = path.join(util.getRequiredEnvParam('RUNNER_TEMP'), uuidV4());
|
||||
const headers: IHeaders = {accept: 'application/octet-stream'};
|
||||
// We only want to provide an authorization header if we are downloading
|
||||
// from the same GitHub instance the Action is running on.
|
||||
// This avoids leaking Enterprise tokens to dotcom.
|
||||
if (codeqlURL.startsWith(INSTANCE_API_URL + "/")) {
|
||||
core.debug('Downloading CodeQL bundle with token.');
|
||||
let token = core.getInput('token', { required: true });
|
||||
headers.authorization = `token ${token}`;
|
||||
} else {
|
||||
core.debug('Downloading CodeQL bundle without token.');
|
||||
}
|
||||
const response: http.HttpClientResponse = await client.get(codeqlURL, headers);
|
||||
if (response.message.statusCode !== 200) {
|
||||
const err = new toolcache.HTTPError(response.message.statusCode);
|
||||
core.info(
|
||||
`Failed to download from "${codeqlURL}". Code(${response.message.statusCode}) Message(${response.message.statusMessage})`
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
const pipeline = globalutil.promisify(stream.pipeline);
|
||||
await io.mkdirP(path.dirname(codeqlPath));
|
||||
await pipeline(response.message, fs.createWriteStream(codeqlPath));
|
||||
core.debug(`CodeQL bundle download to ${codeqlPath} complete.`);
|
||||
|
||||
const codeqlExtracted = await toolcache.extractTar(codeqlPath);
|
||||
codeqlFolder = await toolcache.cacheDir(codeqlExtracted, 'CodeQL', codeqlURLVersion);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user