Search for the CodeQL bundle in multiple places.

This commit is contained in:
Chris Gavin
2020-07-10 11:54:26 +01:00
parent 9769e4a6df
commit 813cb0479f
295 changed files with 134052 additions and 8 deletions

View File

@@ -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);
}