mirror of
https://github.com/github/codeql-action.git
synced 2026-04-27 17:39:15 +00:00
Merge branch 'main' into henrymercer/overlay-repo-property
This commit is contained in:
@@ -77,6 +77,7 @@ export enum Feature {
|
||||
QaTelemetryEnabled = "qa_telemetry_enabled",
|
||||
/** Note that this currently only disables baseline file coverage information. */
|
||||
SkipFileCoverageOnPrs = "skip_file_coverage_on_prs",
|
||||
StartProxyUseFeaturesRelease = "start_proxy_use_features_release",
|
||||
UploadOverlayDbToApi = "upload_overlay_db_to_api",
|
||||
UseRepositoryProperties = "use_repository_properties_v2",
|
||||
ValidateDbConfig = "validate_db_config",
|
||||
@@ -327,6 +328,11 @@ export const featureConfig = {
|
||||
// cannot be found when interpreting results.
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.StartProxyUseFeaturesRelease]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.UploadOverlayDbToApi]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API",
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as core from "@actions/core";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { Feature, FeatureEnablement, initFeatures } from "./feature-flags";
|
||||
import { FeatureEnablement, initFeatures } from "./feature-flags";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
@@ -98,7 +98,7 @@ async function run(startedAt: Date) {
|
||||
};
|
||||
|
||||
// Start the Proxy
|
||||
const proxyBin = await getProxyBinaryPath(logger);
|
||||
const proxyBin = await getProxyBinaryPath(logger, features);
|
||||
const proxyInfo = await startProxy(
|
||||
proxyBin,
|
||||
proxyConfig,
|
||||
|
||||
+171
-37
@@ -7,6 +7,7 @@ import sinon from "sinon";
|
||||
|
||||
import * as apiClient from "./api-client";
|
||||
import * as defaults from "./defaults.json";
|
||||
import { setUpFeatureFlagTests } from "./feature-flags/testing-util";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { getRunnerLogger, Logger } from "./logging";
|
||||
import * as startProxyExports from "./start-proxy";
|
||||
@@ -14,12 +15,19 @@ import { parseLanguage } from "./start-proxy";
|
||||
import * as statusReport from "./status-report";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
createFeatures,
|
||||
getRecordingLogger,
|
||||
makeTestToken,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
withRecordingLoggerAsync,
|
||||
} from "./testing-utils";
|
||||
import { ConfigurationError } from "./util";
|
||||
import {
|
||||
ConfigurationError,
|
||||
GitHubVariant,
|
||||
GitHubVersion,
|
||||
withTmpDir,
|
||||
} from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
@@ -347,8 +355,18 @@ test("parseLanguage", async (t) => {
|
||||
t.deepEqual(parseLanguage(""), undefined);
|
||||
});
|
||||
|
||||
function mockGetReleaseByTag(assets?: Array<{ name: string; url?: string }>) {
|
||||
const mockClient = sinon.stub(apiClient, "getApiClient");
|
||||
function mockGetApiClient(endpoints: any) {
|
||||
return (
|
||||
sinon
|
||||
.stub(apiClient, "getApiClient")
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
.returns({ rest: endpoints } as any)
|
||||
);
|
||||
}
|
||||
|
||||
type ReleaseAssets = Array<{ name: string; url?: string }>;
|
||||
|
||||
function mockGetReleaseByTag(assets?: ReleaseAssets) {
|
||||
const getReleaseByTag =
|
||||
assets === undefined
|
||||
? sinon.stub().rejects()
|
||||
@@ -359,57 +377,82 @@ function mockGetReleaseByTag(assets?: Array<{ name: string; url?: string }>) {
|
||||
url: "GET /repos/:owner/:repo/releases/tags/:tag",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
mockClient.returns({
|
||||
rest: {
|
||||
repos: {
|
||||
getReleaseByTag,
|
||||
},
|
||||
},
|
||||
} as any);
|
||||
return mockClient;
|
||||
return mockGetApiClient({ repos: { getReleaseByTag } });
|
||||
}
|
||||
|
||||
test("getDownloadUrl returns fallback when `getLinkedRelease` rejects", async (t) => {
|
||||
function mockOfflineFeatures(tempDir: string, logger: Logger) {
|
||||
// Using GHES ensures that we are using `OfflineFeatures`.
|
||||
const gitHubVersion = {
|
||||
type: GitHubVariant.GHES,
|
||||
version: "3.0.0",
|
||||
};
|
||||
sinon.stub(apiClient, "getGitHubVersion").resolves(gitHubVersion);
|
||||
|
||||
return setUpFeatureFlagTests(tempDir, logger, gitHubVersion);
|
||||
}
|
||||
|
||||
test("getDownloadUrl returns fallback when `getReleaseByVersion` rejects", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
mockGetReleaseByTag();
|
||||
|
||||
const info = await startProxyExports.getDownloadUrl(getRunnerLogger(true));
|
||||
|
||||
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
|
||||
t.is(
|
||||
info.url,
|
||||
startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()),
|
||||
);
|
||||
});
|
||||
|
||||
test("getDownloadUrl returns fallback when there's no matching release asset", async (t) => {
|
||||
const testAssets = [[], [{ name: "foo" }]];
|
||||
|
||||
for (const assets of testAssets) {
|
||||
const stub = mockGetReleaseByTag(assets);
|
||||
const info = await startProxyExports.getDownloadUrl(getRunnerLogger(true));
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
const info = await startProxyExports.getDownloadUrl(
|
||||
getRunnerLogger(true),
|
||||
features,
|
||||
);
|
||||
|
||||
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
|
||||
t.is(
|
||||
info.url,
|
||||
startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
stub.restore();
|
||||
}
|
||||
test("getDownloadUrl returns fallback when there's no matching release asset", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const testAssets = [[], [{ name: "foo" }]];
|
||||
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
|
||||
for (const assets of testAssets) {
|
||||
const stub = mockGetReleaseByTag(assets);
|
||||
const info = await startProxyExports.getDownloadUrl(
|
||||
getRunnerLogger(true),
|
||||
features,
|
||||
);
|
||||
|
||||
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
|
||||
t.is(
|
||||
info.url,
|
||||
startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()),
|
||||
);
|
||||
|
||||
stub.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("getDownloadUrl returns matching release asset", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const assets = [
|
||||
{ name: "foo", url: "other-url" },
|
||||
{ name: startProxyExports.getProxyPackage(), url: "url-we-want" },
|
||||
];
|
||||
mockGetReleaseByTag(assets);
|
||||
|
||||
const info = await startProxyExports.getDownloadUrl(getRunnerLogger(true));
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
const info = await startProxyExports.getDownloadUrl(
|
||||
getRunnerLogger(true),
|
||||
features,
|
||||
);
|
||||
|
||||
t.is(info.version, defaults.cliVersion);
|
||||
t.is(info.url, "url-we-want");
|
||||
t.is(info.version, defaults.cliVersion);
|
||||
t.is(info.url, "url-we-want");
|
||||
});
|
||||
});
|
||||
|
||||
test("credentialToStr - hides passwords", (t) => {
|
||||
@@ -560,13 +603,15 @@ test(
|
||||
);
|
||||
|
||||
test("getProxyBinaryPath - returns path from tool cache if available", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
mockGetReleaseByTag();
|
||||
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const toolcachePath = "/path/to/proxy/dir";
|
||||
sinon.stub(toolcache, "find").returns(toolcachePath);
|
||||
|
||||
const path = await startProxyExports.getProxyBinaryPath(logger);
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
const path = await startProxyExports.getProxyBinaryPath(logger, features);
|
||||
|
||||
t.assert(path);
|
||||
t.is(
|
||||
@@ -577,12 +622,80 @@ test("getProxyBinaryPath - returns path from tool cache if available", async (t)
|
||||
});
|
||||
|
||||
test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const downloadUrl = "url-we-want";
|
||||
mockGetReleaseByTag([
|
||||
{ name: startProxyExports.getProxyPackage(), url: downloadUrl },
|
||||
]);
|
||||
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
const toolcachePath = "/path/to/proxy/dir";
|
||||
const find = sinon.stub(toolcache, "find").returns("");
|
||||
const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({
|
||||
auth: "",
|
||||
url: "",
|
||||
apiURL: "",
|
||||
});
|
||||
const getAuthorizationHeaderFor = sinon
|
||||
.stub(apiClient, "getAuthorizationHeaderFor")
|
||||
.returns(undefined);
|
||||
const archivePath = "/path/to/archive";
|
||||
const downloadTool = sinon
|
||||
.stub(toolcache, "downloadTool")
|
||||
.resolves(archivePath);
|
||||
const extractedPath = "/path/to/extracted";
|
||||
const extractTar = sinon
|
||||
.stub(toolcache, "extractTar")
|
||||
.resolves(extractedPath);
|
||||
const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath);
|
||||
|
||||
const path = await startProxyExports.getProxyBinaryPath(
|
||||
logger,
|
||||
createFeatures([]),
|
||||
);
|
||||
|
||||
t.assert(find.calledOnce);
|
||||
t.assert(getApiDetails.calledOnce);
|
||||
t.assert(getAuthorizationHeaderFor.calledOnce);
|
||||
t.assert(downloadTool.calledOnceWith(downloadUrl));
|
||||
t.assert(extractTar.calledOnceWith(archivePath));
|
||||
t.assert(cacheDir.calledOnceWith(extractedPath));
|
||||
t.assert(path);
|
||||
t.is(
|
||||
path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Found '${startProxyExports.getProxyPackage()}' in release '${defaults.bundleVersion}' at '${downloadUrl}'`,
|
||||
]);
|
||||
});
|
||||
|
||||
test("getProxyBinaryPath - downloads proxy based on features if not in cache", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const expectedTag = "codeql-bundle-v2.20.1";
|
||||
const expectedParams = {
|
||||
owner: "github",
|
||||
repo: "codeql-action",
|
||||
tag: expectedTag,
|
||||
};
|
||||
const downloadUrl = "url-we-want";
|
||||
const assets = [
|
||||
{
|
||||
name: startProxyExports.getProxyPackage(),
|
||||
url: downloadUrl,
|
||||
},
|
||||
];
|
||||
|
||||
const getReleaseByTag = sinon.stub();
|
||||
getReleaseByTag.withArgs(sinon.match(expectedParams)).resolves({
|
||||
status: 200,
|
||||
data: { assets },
|
||||
headers: {},
|
||||
url: "GET /repos/:owner/:repo/releases/tags/:tag",
|
||||
});
|
||||
mockGetApiClient({ repos: { getReleaseByTag } });
|
||||
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const toolcachePath = "/path/to/proxy/dir";
|
||||
const find = sinon.stub(toolcache, "find").returns("");
|
||||
const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({
|
||||
@@ -603,8 +716,25 @@ test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => {
|
||||
.resolves(extractedPath);
|
||||
const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath);
|
||||
|
||||
const path = await startProxyExports.getProxyBinaryPath(logger);
|
||||
const gitHubVersion: GitHubVersion = {
|
||||
type: GitHubVariant.DOTCOM,
|
||||
};
|
||||
sinon.stub(apiClient, "getGitHubVersion").resolves(gitHubVersion);
|
||||
|
||||
const features = setUpFeatureFlagTests(tempDir, logger, gitHubVersion);
|
||||
sinon.stub(features, "getValue").callsFake(async (_feature, _codeql) => {
|
||||
return true;
|
||||
});
|
||||
const getDefaultCliVersion = sinon
|
||||
.stub(features, "getDefaultCliVersion")
|
||||
.resolves({ cliVersion: "2.20.1", tagName: expectedTag });
|
||||
const path = await startProxyExports.getProxyBinaryPath(logger, features);
|
||||
|
||||
t.assert(getDefaultCliVersion.calledOnce);
|
||||
sinon.assert.calledOnceWithMatch(
|
||||
getReleaseByTag,
|
||||
sinon.match(expectedParams),
|
||||
);
|
||||
t.assert(find.calledOnce);
|
||||
t.assert(getApiDetails.calledOnce);
|
||||
t.assert(getAuthorizationHeaderFor.calledOnce);
|
||||
@@ -618,4 +748,8 @@ test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => {
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
});
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Found '${startProxyExports.getProxyPackage()}' in release '${expectedTag}' at '${downloadUrl}'`,
|
||||
]);
|
||||
});
|
||||
|
||||
+41
-9
@@ -7,10 +7,16 @@ import {
|
||||
getApiClient,
|
||||
getApiDetails,
|
||||
getAuthorizationHeaderFor,
|
||||
getGitHubVersion,
|
||||
} from "./api-client";
|
||||
import * as artifactScanner from "./artifact-scanner";
|
||||
import { Config } from "./config-utils";
|
||||
import * as defaults from "./defaults.json";
|
||||
import {
|
||||
CodeQLDefaultVersionInfo,
|
||||
Feature,
|
||||
FeatureEnablement,
|
||||
} from "./feature-flags";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import {
|
||||
@@ -391,46 +397,69 @@ export function getFallbackUrl(proxyPackage: string): string {
|
||||
|
||||
/**
|
||||
* Uses the GitHub API to obtain information about the CodeQL CLI bundle release
|
||||
* that is pointed at by `defaults.json`.
|
||||
* that is tagged by `version`.
|
||||
*
|
||||
* @returns The response from the GitHub API.
|
||||
*/
|
||||
async function getLinkedRelease() {
|
||||
async function getReleaseByVersion(version: string) {
|
||||
return getApiClient().rest.repos.getReleaseByTag({
|
||||
owner: "github",
|
||||
repo: "codeql-action",
|
||||
tag: defaults.bundleVersion,
|
||||
tag: version,
|
||||
});
|
||||
}
|
||||
|
||||
/** Uses `features` to determine the default CLI version. */
|
||||
async function getCliVersionFromFeatures(
|
||||
features: FeatureEnablement,
|
||||
): Promise<CodeQLDefaultVersionInfo> {
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
return await features.getDefaultCliVersion(gitHubVersion.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the URL of the proxy release asset that we should download if its not
|
||||
* already in the toolcache, and its version.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param features Information about enabled features.
|
||||
* @returns Returns the download URL and version of the proxy package we plan to use.
|
||||
*/
|
||||
export async function getDownloadUrl(
|
||||
logger: Logger,
|
||||
features: FeatureEnablement,
|
||||
): Promise<{ url: string; version: string }> {
|
||||
const proxyPackage = getProxyPackage();
|
||||
|
||||
try {
|
||||
// Try to retrieve information about the CLI bundle release pointed at by `defaults.json`.
|
||||
const cliRelease = await getLinkedRelease();
|
||||
const useFeaturesToDetermineCLI = await features.getValue(
|
||||
Feature.StartProxyUseFeaturesRelease,
|
||||
);
|
||||
|
||||
// Retrieve information about the CLI version we should use. This will be either the linked
|
||||
// version, or the one enabled by FFs.
|
||||
const versionInfo = useFeaturesToDetermineCLI
|
||||
? await getCliVersionFromFeatures(features)
|
||||
: {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
};
|
||||
|
||||
// Try to retrieve information about the CLI bundle release identified by `versionInfo`.
|
||||
const cliRelease = await getReleaseByVersion(versionInfo.tagName);
|
||||
|
||||
// Search the release's assets to find the one we are looking for.
|
||||
for (const asset of cliRelease.data.assets) {
|
||||
if (asset.name === proxyPackage) {
|
||||
logger.info(
|
||||
`Found '${proxyPackage}' in release '${defaults.bundleVersion}' at '${asset.url}'`,
|
||||
`Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'`,
|
||||
);
|
||||
return {
|
||||
url: asset.url,
|
||||
// The `update-job-proxy` doesn't have a version as such. Since we now bundle it
|
||||
// with CodeQL CLI bundle releases, we use the corresponding CLI version to
|
||||
// differentiate between (potentially) different versions of `update-job-proxy`.
|
||||
version: defaults.cliVersion,
|
||||
version: versionInfo.cliVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -548,9 +577,12 @@ export function getProxyFilename() {
|
||||
* @param logger The logger to use.
|
||||
* @returns The path to the proxy binary.
|
||||
*/
|
||||
export async function getProxyBinaryPath(logger: Logger): Promise<string> {
|
||||
export async function getProxyBinaryPath(
|
||||
logger: Logger,
|
||||
features: FeatureEnablement,
|
||||
): Promise<string> {
|
||||
const proxyFileName = getProxyFilename();
|
||||
const proxyInfo = await getDownloadUrl(logger);
|
||||
const proxyInfo = await getDownloadUrl(logger, features);
|
||||
|
||||
let proxyBin = toolcache.find(proxyFileName, proxyInfo.version);
|
||||
if (!proxyBin) {
|
||||
|
||||
Reference in New Issue
Block a user