Merge branch 'main' into checkout_v4
This commit is contained in:
commit
66572c69b0
138 changed files with 119004 additions and 1814 deletions
|
|
@ -1 +1 @@
|
|||
{"maximumVersion": "3.11", "minimumVersion": "3.6"}
|
||||
{"maximumVersion": "3.11", "minimumVersion": "3.7"}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import { getCodeQL } from "./codeql";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { getTemporaryDirectory, getWorkflowEventName } from "./actions-util";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { CodeQL, getCodeQL } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { Language, isTracedLanguage } from "./languages";
|
||||
import { Feature, featureConfig, Features } from "./feature-flags";
|
||||
import { isTracedLanguage, Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import { parseRepositoryNwo } from "./repository";
|
||||
import { getRequiredEnvParam } from "./util";
|
||||
|
||||
export async function determineAutobuildLanguages(
|
||||
config: configUtils.Config,
|
||||
|
|
@ -91,6 +98,47 @@ export async function determineAutobuildLanguages(
|
|||
return languages;
|
||||
}
|
||||
|
||||
async function setupCppAutobuild(codeql: CodeQL, logger: Logger) {
|
||||
const envVar = featureConfig[Feature.CppDependencyInstallation].envVar;
|
||||
const featureName = "C++ automatic installation of dependencies";
|
||||
const envDoc =
|
||||
"https://docs.github.com/en/actions/learn-github-actions/variables#defining-environment-variables-for-a-single-workflow";
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
const repositoryNwo = parseRepositoryNwo(
|
||||
getRequiredEnvParam("GITHUB_REPOSITORY"),
|
||||
);
|
||||
const features = new Features(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
logger,
|
||||
);
|
||||
if (await features.getValue(Feature.CppDependencyInstallation, codeql)) {
|
||||
// disable autoinstall on self-hosted runners unless explicitly requested
|
||||
if (
|
||||
process.env["RUNNER_ENVIRONMENT"] === "self-hosted" &&
|
||||
process.env[envVar] !== "true"
|
||||
) {
|
||||
logger.info(
|
||||
`Disabling ${featureName} as we are on a self-hosted runner.${
|
||||
getWorkflowEventName() !== "dynamic"
|
||||
? ` To override this, set the ${envVar} environment variable to 'true' in your workflow (see ${envDoc}).`
|
||||
: ""
|
||||
}`,
|
||||
);
|
||||
core.exportVariable(envVar, "false");
|
||||
} else {
|
||||
logger.info(
|
||||
`Enabling ${featureName}. This can be disabled by setting the ${envVar} environment variable to 'false' (see ${envDoc}).`,
|
||||
);
|
||||
core.exportVariable(envVar, "true");
|
||||
}
|
||||
} else {
|
||||
logger.info(`Disabling ${featureName}.`);
|
||||
core.exportVariable(envVar, "false");
|
||||
}
|
||||
}
|
||||
|
||||
export async function runAutobuild(
|
||||
language: Language,
|
||||
config: configUtils.Config,
|
||||
|
|
@ -98,6 +146,9 @@ export async function runAutobuild(
|
|||
) {
|
||||
logger.startGroup(`Attempting to automatically build ${language} code`);
|
||||
const codeQL = await getCodeQL(config.codeQLCmd);
|
||||
if (language === Language.cpp) {
|
||||
await setupCppAutobuild(codeQL, logger);
|
||||
}
|
||||
await codeQL.runAutobuild(language);
|
||||
logger.endGroup();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -564,7 +564,7 @@ test("databaseInitCluster() without injected codescanning config", async (t) =>
|
|||
await util.withTmpDir(async (tempDir) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||
sinon.stub(codeqlObject, "getVersion").resolves("2.9.4");
|
||||
sinon.stub(codeqlObject, "getVersion").resolves("2.10.5");
|
||||
// safeWhich throws because of the test CodeQL object.
|
||||
sinon.stub(safeWhich, "safeWhich").resolves("");
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
Feature,
|
||||
FeatureEnablement,
|
||||
useCodeScanningConfigInCli,
|
||||
CODEQL_VERSION_SUBLANGUAGE_FILE_COVERAGE,
|
||||
} from "./feature-flags";
|
||||
import { isTracedLanguage, Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
|
|
@ -276,7 +277,7 @@ let cachedCodeQL: CodeQL | undefined = undefined;
|
|||
* The version flags below can be used to conditionally enable certain features
|
||||
* on versions newer than this.
|
||||
*/
|
||||
const CODEQL_MINIMUM_VERSION = "2.9.4";
|
||||
const CODEQL_MINIMUM_VERSION = "2.10.5";
|
||||
|
||||
/**
|
||||
* This version will shortly become the oldest version of CodeQL that the Action will run with.
|
||||
|
|
@ -293,21 +294,13 @@ const GHES_VERSION_MOST_RECENTLY_DEPRECATED = "3.6";
|
|||
*/
|
||||
const GHES_MOST_RECENT_DEPRECATION_DATE = "2023-09-12";
|
||||
|
||||
/**
|
||||
/*
|
||||
* Versions of CodeQL that version-flag certain functionality in the Action.
|
||||
* For convenience, please keep these in descending order. Once a version
|
||||
* flag is older than the oldest supported version above, it may be removed.
|
||||
*/
|
||||
const CODEQL_VERSION_LUA_TRACER_CONFIG = "2.10.0";
|
||||
const CODEQL_VERSION_LUA_TRACING_GO_WINDOWS_FIXED = "2.10.4";
|
||||
export const CODEQL_VERSION_GHES_PACK_DOWNLOAD = "2.10.4";
|
||||
const CODEQL_VERSION_FILE_BASELINE_INFORMATION = "2.11.3";
|
||||
|
||||
/**
|
||||
* Previous versions had the option already, but were missing the
|
||||
* --extractor-options-verbosity that we need.
|
||||
*/
|
||||
export const CODEQL_VERSION_BETTER_RESOLVE_LANGUAGES = "2.10.3";
|
||||
const CODEQL_VERSION_FILE_BASELINE_INFORMATION = "2.11.3";
|
||||
|
||||
/**
|
||||
* Versions 2.11.1+ of the CodeQL Bundle include a `security-experimental` built-in query suite for
|
||||
|
|
@ -558,24 +551,6 @@ export async function getCodeQLForCmd(
|
|||
extraArgs.push("--begin-tracing");
|
||||
extraArgs.push(...(await getTrapCachingExtractorConfigArgs(config)));
|
||||
extraArgs.push(`--trace-process-name=${processName}`);
|
||||
if (
|
||||
// There's a bug in Lua tracing for Go on Windows in versions earlier than
|
||||
// `CODEQL_VERSION_LUA_TRACING_GO_WINDOWS_FIXED`, so don't use Lua tracing
|
||||
// when tracing Go on Windows on these CodeQL versions.
|
||||
(await util.codeQlVersionAbove(
|
||||
this,
|
||||
CODEQL_VERSION_LUA_TRACER_CONFIG,
|
||||
)) &&
|
||||
config.languages.includes(Language.go) &&
|
||||
isTracedLanguage(Language.go) &&
|
||||
process.platform === "win32" &&
|
||||
!(await util.codeQlVersionAbove(
|
||||
this,
|
||||
CODEQL_VERSION_LUA_TRACING_GO_WINDOWS_FIXED,
|
||||
))
|
||||
) {
|
||||
extraArgs.push("--no-internal-use-lua-tracing");
|
||||
}
|
||||
}
|
||||
|
||||
// A code scanning config file is only generated if the CliConfigFileEnabled feature flag is enabled.
|
||||
|
|
@ -611,6 +586,19 @@ export async function getCodeQLForCmd(
|
|||
extraArgs.push("--calculate-language-specific-baseline");
|
||||
}
|
||||
|
||||
if (
|
||||
await features.getValue(Feature.SublanguageFileCoverageEnabled, this)
|
||||
) {
|
||||
extraArgs.push("--sublanguage-file-coverage");
|
||||
} else if (
|
||||
await util.codeQlVersionAbove(
|
||||
this,
|
||||
CODEQL_VERSION_SUBLANGUAGE_FILE_COVERAGE,
|
||||
)
|
||||
) {
|
||||
extraArgs.push("--no-sublanguage-file-coverage");
|
||||
}
|
||||
|
||||
await runTool(
|
||||
cmd,
|
||||
[
|
||||
|
|
|
|||
|
|
@ -7,12 +7,7 @@ import * as yaml from "js-yaml";
|
|||
import * as sinon from "sinon";
|
||||
|
||||
import * as api from "./api-client";
|
||||
import {
|
||||
CODEQL_VERSION_GHES_PACK_DOWNLOAD,
|
||||
getCachedCodeQL,
|
||||
PackDownloadOutput,
|
||||
setCodeQL,
|
||||
} from "./codeql";
|
||||
import { getCachedCodeQL, PackDownloadOutput, setCodeQL } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { Feature } from "./feature-flags";
|
||||
import { Language } from "./languages";
|
||||
|
|
@ -2217,20 +2212,20 @@ test(
|
|||
"0.0.1",
|
||||
);
|
||||
// Test that ML-powered queries ~0.3.0 are run on all platforms running `security-extended` on
|
||||
// CodeQL CLI 2.9.4+.
|
||||
// CodeQL CLI 2.10.5+.
|
||||
test(
|
||||
mlPoweredQueriesMacro,
|
||||
"2.9.4",
|
||||
"2.10.5",
|
||||
true,
|
||||
undefined,
|
||||
"security-extended",
|
||||
"~0.3.0",
|
||||
);
|
||||
// Test that ML-powered queries ~0.3.0 are run on all platforms running `security-and-quality` on
|
||||
// CodeQL CLI 2.9.4+.
|
||||
// CodeQL CLI 2.10.5+.
|
||||
test(
|
||||
mlPoweredQueriesMacro,
|
||||
"2.9.4",
|
||||
"2.10.5",
|
||||
true,
|
||||
undefined,
|
||||
"security-and-quality",
|
||||
|
|
@ -2554,48 +2549,6 @@ test("downloadPacks-with-registries", async (t) => {
|
|||
});
|
||||
});
|
||||
|
||||
test("downloadPacks-with-registries fails on 2.10.3", async (t) => {
|
||||
// same thing, but this time include a registries block and
|
||||
// associated env vars
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
process.env.GITHUB_TOKEN = "not-a-token";
|
||||
process.env.CODEQL_REGISTRIES_AUTH = "not-a-registries-auth";
|
||||
const logger = getRunnerLogger(true);
|
||||
|
||||
const registriesInput = yaml.dump([
|
||||
{
|
||||
url: "http://ghcr.io",
|
||||
packages: ["codeql/*", "codeql-testing/*"],
|
||||
token: "not-a-token",
|
||||
},
|
||||
{
|
||||
url: "https://containers.GHEHOSTNAME1/v2/",
|
||||
packages: "semmle/*",
|
||||
token: "still-not-a-token",
|
||||
},
|
||||
]);
|
||||
|
||||
const codeQL = setCodeQL({
|
||||
getVersion: () => Promise.resolve("2.10.3"),
|
||||
});
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
return await configUtils.downloadPacks(
|
||||
codeQL,
|
||||
[Language.javascript, Language.java, Language.python],
|
||||
{},
|
||||
sampleApiDetails,
|
||||
registriesInput,
|
||||
tmpDir,
|
||||
logger,
|
||||
);
|
||||
},
|
||||
{ instanceOf: Error },
|
||||
"'registries' input is not supported on CodeQL versions less than 2.10.4.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("downloadPacks-with-registries fails with invalid registries block", async (t) => {
|
||||
// same thing, but this time include a registries block and
|
||||
// associated env vars
|
||||
|
|
@ -2638,51 +2591,12 @@ test("downloadPacks-with-registries fails with invalid registries block", async
|
|||
});
|
||||
});
|
||||
|
||||
// the happy path for generateRegistries is already tested in downloadPacks.
|
||||
// these following tests are for the error cases and when nothing is generated.
|
||||
test("no generateRegistries when CLI is too old", async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const registriesInput = yaml.dump([
|
||||
{
|
||||
// no slash
|
||||
url: "http://ghcr.io",
|
||||
packages: ["codeql/*", "codeql-testing/*"],
|
||||
token: "not-a-token",
|
||||
},
|
||||
]);
|
||||
const codeQL = setCodeQL({
|
||||
// Accepted CLI versions are 2.10.4 or higher
|
||||
getVersion: () => Promise.resolve("2.10.3"),
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
await t.throwsAsync(
|
||||
async () =>
|
||||
await configUtils.generateRegistries(
|
||||
registriesInput,
|
||||
codeQL,
|
||||
tmpDir,
|
||||
logger,
|
||||
),
|
||||
undefined,
|
||||
"'registries' input is not supported on CodeQL versions less than 2.10.4.",
|
||||
);
|
||||
});
|
||||
});
|
||||
test("no generateRegistries when registries is undefined", async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const registriesInput = undefined;
|
||||
const codeQL = setCodeQL({
|
||||
// Accepted CLI versions are 2.10.4 or higher
|
||||
getVersion: () => Promise.resolve(CODEQL_VERSION_GHES_PACK_DOWNLOAD),
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const { registriesAuthTokens, qlconfigFile } =
|
||||
await configUtils.generateRegistries(
|
||||
registriesInput,
|
||||
codeQL,
|
||||
tmpDir,
|
||||
logger,
|
||||
);
|
||||
await configUtils.generateRegistries(registriesInput, tmpDir, logger);
|
||||
|
||||
t.is(registriesAuthTokens, undefined);
|
||||
t.is(qlconfigFile, undefined);
|
||||
|
|
@ -2699,18 +2613,9 @@ test("generateRegistries prefers original CODEQL_REGISTRIES_AUTH", async (t) =>
|
|||
token: "not-a-token",
|
||||
},
|
||||
]);
|
||||
const codeQL = setCodeQL({
|
||||
// Accepted CLI versions are 2.10.4 or higher
|
||||
getVersion: () => Promise.resolve(CODEQL_VERSION_GHES_PACK_DOWNLOAD),
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const { registriesAuthTokens, qlconfigFile } =
|
||||
await configUtils.generateRegistries(
|
||||
registriesInput,
|
||||
codeQL,
|
||||
tmpDir,
|
||||
logger,
|
||||
);
|
||||
await configUtils.generateRegistries(registriesInput, tmpDir, logger);
|
||||
|
||||
t.is(registriesAuthTokens, "original");
|
||||
t.is(qlconfigFile, path.join(tmpDir, "qlconfig.yml"));
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import * as semver from "semver";
|
|||
import * as api from "./api-client";
|
||||
import {
|
||||
CodeQL,
|
||||
CODEQL_VERSION_GHES_PACK_DOWNLOAD,
|
||||
CODEQL_VERSION_LANGUAGE_ALIASING,
|
||||
CODEQL_VERSION_SECURITY_EXPERIMENTAL_SUITE,
|
||||
ResolveQueriesOutput,
|
||||
|
|
@ -1977,7 +1976,6 @@ export async function downloadPacks(
|
|||
// This code path is only used when config parsing occurs in the Action.
|
||||
const { registriesAuthTokens, qlconfigFile } = await generateRegistries(
|
||||
registriesInput,
|
||||
codeQL,
|
||||
tempDir,
|
||||
logger,
|
||||
);
|
||||
|
|
@ -2033,7 +2031,6 @@ export async function downloadPacks(
|
|||
*/
|
||||
export async function generateRegistries(
|
||||
registriesInput: string | undefined,
|
||||
codeQL: CodeQL,
|
||||
tempDir: string,
|
||||
logger: Logger,
|
||||
) {
|
||||
|
|
@ -2041,14 +2038,6 @@ export async function generateRegistries(
|
|||
let registriesAuthTokens: string | undefined;
|
||||
let qlconfigFile: string | undefined;
|
||||
if (registries) {
|
||||
if (
|
||||
!(await codeQlVersionAbove(codeQL, CODEQL_VERSION_GHES_PACK_DOWNLOAD))
|
||||
) {
|
||||
throw new UserError(
|
||||
`The 'registries' input is not supported on CodeQL CLI versions earlier than ${CODEQL_VERSION_GHES_PACK_DOWNLOAD}. Please upgrade to CodeQL CLI version ${CODEQL_VERSION_GHES_PACK_DOWNLOAD} or later.`,
|
||||
);
|
||||
}
|
||||
|
||||
// generate a qlconfig.yml file to hold the registry configs.
|
||||
const qlconfig = createRegistriesBlock(registries);
|
||||
qlconfigFile = path.join(tempDir, "qlconfig.yml");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"bundleVersion": "codeql-bundle-v2.14.5",
|
||||
"cliVersion": "2.14.5",
|
||||
"priorBundleVersion": "codeql-bundle-v2.14.4",
|
||||
"priorCliVersion": "2.14.4"
|
||||
"bundleVersion": "codeql-bundle-v2.14.6",
|
||||
"cliVersion": "2.14.6",
|
||||
"priorBundleVersion": "codeql-bundle-v2.14.5",
|
||||
"priorCliVersion": "2.14.5"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,15 @@ export const CODEQL_VERSION_BUNDLE_SEMANTICALLY_VERSIONED = "2.13.4";
|
|||
export const CODEQL_VERSION_ANALYSIS_SUMMARY_V2 = "2.14.0";
|
||||
|
||||
/**
|
||||
* Versions 2.14.0+ of the CodeQL CLI support intra-layer parallelism (aka fine-grained parallelism) options.
|
||||
* Versions 2.14.0+ of the CodeQL CLI support intra-layer parallelism (aka fine-grained parallelism) options, but we
|
||||
* limit to 2.14.6 onwards, since that's the version that has mitigations against OOM failures.
|
||||
*/
|
||||
export const CODEQL_VERSION_INTRA_LAYER_PARALLELISM = "2.14.0";
|
||||
export const CODEQL_VERSION_INTRA_LAYER_PARALLELISM = "2.14.6";
|
||||
|
||||
/**
|
||||
* Versions 2.15.0+ of the CodeQL CLI support sub-language file coverage information.
|
||||
*/
|
||||
export const CODEQL_VERSION_SUBLANGUAGE_FILE_COVERAGE = "2.15.0";
|
||||
|
||||
export interface CodeQLDefaultVersionInfo {
|
||||
cliVersion: string;
|
||||
|
|
@ -51,12 +57,14 @@ export enum Feature {
|
|||
AnalysisSummaryV2Enabled = "analysis_summary_v2_enabled",
|
||||
CliConfigFileEnabled = "cli_config_file_enabled",
|
||||
CodeqlJavaLombokEnabled = "codeql_java_lombok_enabled",
|
||||
CppDependencyInstallation = "cpp_dependency_installation_enabled",
|
||||
DisableKotlinAnalysisEnabled = "disable_kotlin_analysis_enabled",
|
||||
DisablePythonDependencyInstallationEnabled = "disable_python_dependency_installation_enabled",
|
||||
EvaluatorIntraLayerParallelismEnabled = "evaluator_intra_layer_parallelism_enabled",
|
||||
ExportDiagnosticsEnabled = "export_diagnostics_enabled",
|
||||
MlPoweredQueriesEnabled = "ml_powered_queries_enabled",
|
||||
QaTelemetryEnabled = "qa_telemetry_enabled",
|
||||
SublanguageFileCoverageEnabled = "sublanguage_file_coverage_enabled",
|
||||
UploadFailedSarifEnabled = "upload_failed_sarif_enabled",
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +82,11 @@ export const featureConfig: Record<
|
|||
minimumVersion: "2.14.0",
|
||||
defaultValue: false,
|
||||
},
|
||||
[Feature.CppDependencyInstallation]: {
|
||||
envVar: "CODEQL_EXTRACTOR_CPP_AUTOINSTALL_DEPENDENCIES",
|
||||
minimumVersion: "2.15.0",
|
||||
defaultValue: false,
|
||||
},
|
||||
[Feature.DisableKotlinAnalysisEnabled]: {
|
||||
envVar: "CODEQL_DISABLE_KOTLIN_ANALYSIS",
|
||||
minimumVersion: undefined,
|
||||
|
|
@ -104,6 +117,11 @@ export const featureConfig: Record<
|
|||
minimumVersion: undefined,
|
||||
defaultValue: false,
|
||||
},
|
||||
[Feature.SublanguageFileCoverageEnabled]: {
|
||||
envVar: "CODEQL_ACTION_SUBLANGUAGE_FILE_COVERAGE",
|
||||
minimumVersion: CODEQL_VERSION_SUBLANGUAGE_FILE_COVERAGE,
|
||||
defaultValue: false,
|
||||
},
|
||||
[Feature.UploadFailedSarifEnabled]: {
|
||||
envVar: "CODEQL_ACTION_UPLOAD_FAILED_SARIF",
|
||||
minimumVersion: "2.11.3",
|
||||
|
|
|
|||
|
|
@ -217,8 +217,6 @@ async function run() {
|
|||
core.exportVariable(EnvVar.JOB_RUN_UUID, uuidV4());
|
||||
|
||||
try {
|
||||
const workflowErrors = await validateWorkflow(logger);
|
||||
|
||||
if (
|
||||
!(await sendStatusReport(
|
||||
await createStatusReportBase(
|
||||
|
|
@ -226,7 +224,6 @@ async function run() {
|
|||
"starting",
|
||||
startedAt,
|
||||
await checkDiskUsage(logger),
|
||||
workflowErrors,
|
||||
),
|
||||
))
|
||||
) {
|
||||
|
|
@ -250,6 +247,8 @@ async function run() {
|
|||
toolsVersion = initCodeQLResult.toolsVersion;
|
||||
toolsSource = initCodeQLResult.toolsSource;
|
||||
|
||||
await validateWorkflow(codeql, logger);
|
||||
|
||||
config = await initConfig(
|
||||
getOptionalInput("languages"),
|
||||
getOptionalInput("queries"),
|
||||
|
|
|
|||
|
|
@ -118,7 +118,6 @@ export async function runInit(
|
|||
({ registriesAuthTokens, qlconfigFile } =
|
||||
await configUtils.generateRegistries(
|
||||
registriesInput,
|
||||
codeql,
|
||||
config.tempDir,
|
||||
logger,
|
||||
));
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import * as path from "path";
|
|||
import * as cache from "@actions/cache";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { CodeQL, CODEQL_VERSION_BETTER_RESOLVE_LANGUAGES } from "./codeql";
|
||||
import { CodeQL } from "./codeql";
|
||||
import type { Config } from "./config-utils";
|
||||
import { Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import { codeQlVersionAbove, tryGetFolderBytes, withTimeout } from "./util";
|
||||
import { tryGetFolderBytes, withTimeout } from "./util";
|
||||
|
||||
// This constant should be bumped if we make a breaking change
|
||||
// to how the CodeQL Action stores or retrieves the TRAP cache,
|
||||
|
|
@ -164,10 +164,6 @@ export async function getLanguagesSupportingCaching(
|
|||
logger: Logger,
|
||||
): Promise<Language[]> {
|
||||
const result: Language[] = [];
|
||||
if (
|
||||
!(await codeQlVersionAbove(codeql, CODEQL_VERSION_BETTER_RESOLVE_LANGUAGES))
|
||||
)
|
||||
return result;
|
||||
const resolveResult = await codeql.betterResolveLanguages();
|
||||
outer: for (const lang of languages) {
|
||||
const extractorsForLanguage = resolveResult.extractors[lang];
|
||||
|
|
|
|||
|
|
@ -260,6 +260,7 @@ function getCgroupMemoryLimitBytes(
|
|||
}
|
||||
|
||||
const limit = Number(fs.readFileSync(limitFile, "utf8"));
|
||||
|
||||
if (!Number.isInteger(limit)) {
|
||||
logger.debug(
|
||||
`While resolving RAM, ignored the file ${limitFile} that may contain a cgroup memory limit ` +
|
||||
|
|
@ -269,6 +270,14 @@ function getCgroupMemoryLimitBytes(
|
|||
}
|
||||
|
||||
const displayLimit = `${Math.floor(limit / (1024 * 1024))} MiB`;
|
||||
if (limit > os.totalmem()) {
|
||||
logger.debug(
|
||||
`While resolving RAM, ignored the file ${limitFile} that may contain a cgroup memory limit as ` +
|
||||
`its contents ${displayLimit} were greater than the total amount of system memory.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (limit < MINIMUM_CGROUP_MEMORY_LIMIT_BYTES) {
|
||||
logger.info(
|
||||
`While resolving RAM, ignored a cgroup limit of ${displayLimit} in ${limitFile} as it was below ${
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import test from "ava";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import * as yaml from "js-yaml";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { getCodeQLForTesting } from "./codeql";
|
||||
import { setupTests } from "./testing-utils";
|
||||
import {
|
||||
CodedError,
|
||||
|
|
@ -22,227 +24,395 @@ function errorCodes(
|
|||
|
||||
setupTests(test);
|
||||
|
||||
test("getWorkflowErrors() when on is empty", (t) => {
|
||||
const errors = getWorkflowErrors({ on: {} });
|
||||
test("getWorkflowErrors() when on is empty", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
{ on: {} },
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when on.push is an array missing pull_request", (t) => {
|
||||
const errors = getWorkflowErrors({ on: ["push"] });
|
||||
test("getWorkflowErrors() when on.push is an array missing pull_request", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
{ on: ["push"] },
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when on.push is an array missing push", (t) => {
|
||||
const errors = getWorkflowErrors({ on: ["pull_request"] });
|
||||
test("getWorkflowErrors() when on.push is an array missing push", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
{ on: ["pull_request"] },
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, [WorkflowErrors.MissingPushHook]));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when on.push is valid", (t) => {
|
||||
const errors = getWorkflowErrors({
|
||||
on: ["push", "pull_request"],
|
||||
});
|
||||
test("getWorkflowErrors() when on.push is valid", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
{
|
||||
on: ["push", "pull_request"],
|
||||
},
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when on.push is a valid superset", (t) => {
|
||||
const errors = getWorkflowErrors({
|
||||
on: ["push", "pull_request", "schedule"],
|
||||
});
|
||||
test("getWorkflowErrors() when on.push is a valid superset", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
{
|
||||
on: ["push", "pull_request", "schedule"],
|
||||
},
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when on.push is a correct object", (t) => {
|
||||
const errors = getWorkflowErrors({
|
||||
on: { push: { branches: ["main"] }, pull_request: { branches: ["main"] } },
|
||||
});
|
||||
test("getWorkflowErrors() when on.push is a correct object", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
{
|
||||
on: {
|
||||
push: { branches: ["main"] },
|
||||
pull_request: { branches: ["main"] },
|
||||
},
|
||||
},
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when on.pull_requests is a string and correct", (t) => {
|
||||
const errors = getWorkflowErrors({
|
||||
on: { push: { branches: "*" }, pull_request: { branches: "*" } },
|
||||
});
|
||||
test("getWorkflowErrors() when on.pull_requests is a string and correct", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
{
|
||||
on: { push: { branches: "*" }, pull_request: { branches: "*" } },
|
||||
},
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when on.push is correct with empty objects", (t) => {
|
||||
const errors = getWorkflowErrors(
|
||||
test("getWorkflowErrors() when on.push is correct with empty objects", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when on.push is not mismatched", (t) => {
|
||||
const errors = getWorkflowErrors({
|
||||
on: {
|
||||
push: { branches: ["main", "feature"] },
|
||||
pull_request: { branches: ["main"] },
|
||||
test("getWorkflowErrors() when on.push is not mismatched", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
{
|
||||
on: {
|
||||
push: { branches: ["main", "feature"] },
|
||||
pull_request: { branches: ["main"] },
|
||||
},
|
||||
},
|
||||
});
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() for a range of malformed workflows", (t) => {
|
||||
test("getWorkflowErrors() for a range of malformed workflows", async (t) => {
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
getWorkflowErrors({
|
||||
on: {
|
||||
push: 1,
|
||||
pull_request: 1,
|
||||
},
|
||||
} as Workflow),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
getWorkflowErrors({
|
||||
on: 1,
|
||||
} as Workflow),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
getWorkflowErrors({
|
||||
on: 1,
|
||||
jobs: 1,
|
||||
} as any),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
getWorkflowErrors({
|
||||
on: 1,
|
||||
jobs: [1],
|
||||
} as any),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
getWorkflowErrors({
|
||||
on: 1,
|
||||
jobs: { 1: 1 },
|
||||
} as Workflow),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
getWorkflowErrors({
|
||||
on: 1,
|
||||
jobs: { test: 1 },
|
||||
} as Workflow),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
getWorkflowErrors({
|
||||
on: 1,
|
||||
jobs: { test: [1] },
|
||||
} as Workflow),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
getWorkflowErrors({
|
||||
on: 1,
|
||||
jobs: { test: { steps: 1 } },
|
||||
} as any),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
getWorkflowErrors({
|
||||
on: 1,
|
||||
jobs: { test: { steps: [{ notrun: "git checkout HEAD^2" }] } },
|
||||
} as any),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
getWorkflowErrors({
|
||||
on: 1,
|
||||
jobs: { test: [undefined] },
|
||||
} as Workflow),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(getWorkflowErrors(1 as Workflow), []));
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
getWorkflowErrors({
|
||||
on: {
|
||||
push: {
|
||||
branches: 1,
|
||||
await getWorkflowErrors(
|
||||
{
|
||||
on: {
|
||||
push: 1,
|
||||
pull_request: 1,
|
||||
},
|
||||
pull_request: {
|
||||
branches: 1,
|
||||
} as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
await getWorkflowErrors(
|
||||
{
|
||||
on: 1,
|
||||
} as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
await getWorkflowErrors(
|
||||
{
|
||||
on: 1,
|
||||
jobs: 1,
|
||||
} as unknown as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
await getWorkflowErrors(
|
||||
{
|
||||
on: 1,
|
||||
jobs: [1],
|
||||
} as unknown as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
await getWorkflowErrors(
|
||||
{
|
||||
on: 1,
|
||||
jobs: { 1: 1 },
|
||||
} as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
await getWorkflowErrors(
|
||||
{
|
||||
on: 1,
|
||||
jobs: { test: 1 },
|
||||
} as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
await getWorkflowErrors(
|
||||
{
|
||||
on: 1,
|
||||
jobs: { test: [1] },
|
||||
} as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
await getWorkflowErrors(
|
||||
{
|
||||
on: 1,
|
||||
jobs: { test: { steps: 1 } },
|
||||
} as unknown as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
await getWorkflowErrors(
|
||||
{
|
||||
on: 1,
|
||||
jobs: { test: { steps: [{ notrun: "git checkout HEAD^2" }] } },
|
||||
} as unknown as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
await getWorkflowErrors(
|
||||
{
|
||||
on: 1,
|
||||
jobs: { test: [undefined] },
|
||||
} as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
await getWorkflowErrors(1 as Workflow, await getCodeQLForTesting()),
|
||||
[],
|
||||
),
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
await getWorkflowErrors(
|
||||
{
|
||||
on: {
|
||||
push: {
|
||||
branches: 1,
|
||||
},
|
||||
pull_request: {
|
||||
branches: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any),
|
||||
} as unknown as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when on.pull_request for wildcard branches", (t) => {
|
||||
const errors = getWorkflowErrors({
|
||||
on: {
|
||||
push: { branches: ["feature/*"] },
|
||||
pull_request: { branches: "feature/moose" },
|
||||
test("getWorkflowErrors() when on.pull_request for wildcard branches", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
{
|
||||
on: {
|
||||
push: { branches: ["feature/*"] },
|
||||
pull_request: { branches: "feature/moose" },
|
||||
},
|
||||
},
|
||||
});
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when HEAD^2 is checked out", (t) => {
|
||||
test("getWorkflowErrors() when HEAD^2 is checked out", async (t) => {
|
||||
process.env.GITHUB_JOB = "test";
|
||||
|
||||
const errors = getWorkflowErrors({
|
||||
on: ["push", "pull_request"],
|
||||
jobs: { test: { steps: [{ run: "git checkout HEAD^2" }] } },
|
||||
});
|
||||
const errors = await getWorkflowErrors(
|
||||
{
|
||||
on: ["push", "pull_request"],
|
||||
jobs: { test: { steps: [{ run: "git checkout HEAD^2" }] } },
|
||||
},
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, [WorkflowErrors.CheckoutWrongHead]));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() produces an error for workflow with language name and its alias", async (t) => {
|
||||
await testLanguageAliases(
|
||||
t,
|
||||
["java", "kotlin"],
|
||||
{ java: ["java-kotlin", "kotlin"] },
|
||||
[
|
||||
"CodeQL language 'java' is referenced by more than one entry in the 'language' matrix " +
|
||||
"parameter for job 'test'. This may result in duplicate alerts. Please edit the 'language' " +
|
||||
"matrix parameter to keep only one of the following: 'java', 'kotlin'.",
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() produces an error for workflow with two aliases same language", async (t) => {
|
||||
await testLanguageAliases(
|
||||
t,
|
||||
["java-kotlin", "kotlin"],
|
||||
{ java: ["java-kotlin", "kotlin"] },
|
||||
[
|
||||
"CodeQL language 'java' is referenced by more than one entry in the 'language' matrix " +
|
||||
"parameter for job 'test'. This may result in duplicate alerts. Please edit the 'language' " +
|
||||
"matrix parameter to keep only one of the following: 'java-kotlin', 'kotlin'.",
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() does not produce an error for workflow with two distinct languages", async (t) => {
|
||||
await testLanguageAliases(
|
||||
t,
|
||||
["java", "typescript"],
|
||||
{
|
||||
java: ["java-kotlin", "kotlin"],
|
||||
javascript: ["javascript-typescript", "typescript"],
|
||||
},
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() does not produce an error if codeql doesn't support language aliases", async (t) => {
|
||||
await testLanguageAliases(t, ["java-kotlin", "kotlin"], undefined, []);
|
||||
});
|
||||
|
||||
async function testLanguageAliases(
|
||||
t: ExecutionContext<unknown>,
|
||||
matrixLanguages: string[],
|
||||
aliases: { [languageName: string]: string[] } | undefined,
|
||||
expectedErrorMessages: string[],
|
||||
) {
|
||||
process.env.GITHUB_JOB = "test";
|
||||
|
||||
const codeql = await getCodeQLForTesting();
|
||||
sinon.stub(codeql, "betterResolveLanguages").resolves({
|
||||
aliases:
|
||||
aliases !== undefined
|
||||
? // Remap from languageName -> aliases to alias -> languageName
|
||||
Object.assign(
|
||||
{},
|
||||
...Object.entries(aliases).flatMap(([language, languageAliases]) =>
|
||||
languageAliases.map((alias) => ({
|
||||
[alias]: language,
|
||||
})),
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
extractors: {
|
||||
java: [
|
||||
{
|
||||
extractor_root: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const errors = await getWorkflowErrors(
|
||||
{
|
||||
on: ["push", "pull_request"],
|
||||
jobs: {
|
||||
test: {
|
||||
strategy: {
|
||||
matrix: {
|
||||
language: matrixLanguages,
|
||||
},
|
||||
},
|
||||
steps: [
|
||||
{ uses: "actions/checkout@v2" },
|
||||
{ uses: "github/codeql-action/init@v2" },
|
||||
{ uses: "github/codeql-action/analyze@v2" },
|
||||
],
|
||||
},
|
||||
},
|
||||
} as Workflow,
|
||||
codeql,
|
||||
);
|
||||
|
||||
t.is(errors.length, expectedErrorMessages.length);
|
||||
t.deepEqual(
|
||||
errors.map((e) => e.message),
|
||||
expectedErrorMessages,
|
||||
);
|
||||
}
|
||||
|
||||
test("formatWorkflowErrors() when there is one error", (t) => {
|
||||
const message = formatWorkflowErrors([WorkflowErrors.CheckoutWrongHead]);
|
||||
t.true(message.startsWith("1 issue was detected with this workflow:"));
|
||||
|
|
@ -297,8 +467,8 @@ test("patternIsSuperset()", (t) => {
|
|||
);
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when branches contain dots", (t) => {
|
||||
const errors = getWorkflowErrors(
|
||||
test("getWorkflowErrors() when branches contain dots", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
on:
|
||||
push:
|
||||
|
|
@ -307,13 +477,14 @@ test("getWorkflowErrors() when branches contain dots", (t) => {
|
|||
# The branches below must be a subset of the branches above
|
||||
branches: [4.1, master]
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when on.push has a trailing comma", (t) => {
|
||||
const errors = getWorkflowErrors(
|
||||
test("getWorkflowErrors() when on.push has a trailing comma", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
name: "CodeQL"
|
||||
on:
|
||||
|
|
@ -323,15 +494,16 @@ test("getWorkflowErrors() when on.push has a trailing comma", (t) => {
|
|||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() should only report the current job's CheckoutWrongHead", (t) => {
|
||||
test("getWorkflowErrors() should only report the current job's CheckoutWrongHead", async (t) => {
|
||||
process.env.GITHUB_JOB = "test";
|
||||
|
||||
const errors = getWorkflowErrors(
|
||||
const errors = await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
name: "CodeQL"
|
||||
on:
|
||||
|
|
@ -352,15 +524,16 @@ test("getWorkflowErrors() should only report the current job's CheckoutWrongHead
|
|||
test3:
|
||||
steps: []
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, [WorkflowErrors.CheckoutWrongHead]));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() should not report a different job's CheckoutWrongHead", (t) => {
|
||||
test("getWorkflowErrors() should not report a different job's CheckoutWrongHead", async (t) => {
|
||||
process.env.GITHUB_JOB = "test3";
|
||||
|
||||
const errors = getWorkflowErrors(
|
||||
const errors = await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
name: "CodeQL"
|
||||
on:
|
||||
|
|
@ -381,29 +554,32 @@ test("getWorkflowErrors() should not report a different job's CheckoutWrongHead"
|
|||
test3:
|
||||
steps: []
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when on is missing", (t) => {
|
||||
const errors = getWorkflowErrors(
|
||||
test("getWorkflowErrors() when on is missing", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
name: "CodeQL"
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() with a different on setup", (t) => {
|
||||
test("getWorkflowErrors() with a different on setup", async (t) => {
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
getWorkflowErrors(
|
||||
await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
name: "CodeQL"
|
||||
on: "workflow_dispatch"
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
|
|
@ -411,11 +587,12 @@ test("getWorkflowErrors() with a different on setup", (t) => {
|
|||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
getWorkflowErrors(
|
||||
await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
name: "CodeQL"
|
||||
on: [workflow_dispatch]
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
|
|
@ -423,28 +600,30 @@ test("getWorkflowErrors() with a different on setup", (t) => {
|
|||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
getWorkflowErrors(
|
||||
await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
name: "CodeQL"
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() should not report an error if PRs are totally unconfigured", (t) => {
|
||||
test("getWorkflowErrors() should not report an error if PRs are totally unconfigured", async (t) => {
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
getWorkflowErrors(
|
||||
await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
name: "CodeQL"
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
|
|
@ -452,11 +631,12 @@ test("getWorkflowErrors() should not report an error if PRs are totally unconfig
|
|||
|
||||
t.deepEqual(
|
||||
...errorCodes(
|
||||
getWorkflowErrors(
|
||||
await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
name: "CodeQL"
|
||||
on: ["push"]
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
),
|
||||
[],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import * as core from "@actions/core";
|
|||
import * as yaml from "js-yaml";
|
||||
|
||||
import * as api from "./api-client";
|
||||
import { CodeQL } from "./codeql";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Logger } from "./logging";
|
||||
import { getRequiredEnvParam, isInTestMode } from "./util";
|
||||
|
|
@ -21,6 +22,7 @@ interface WorkflowJob {
|
|||
name?: string;
|
||||
"runs-on"?: string;
|
||||
steps?: WorkflowJobStep[];
|
||||
strategy?: { matrix: { [key: string]: string[] } };
|
||||
uses?: string;
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +106,37 @@ export const WorkflowErrors = toCodedErrors({
|
|||
CheckoutWrongHead: `git checkout HEAD^2 is no longer necessary. Please remove this step as Code Scanning recommends analyzing the merge commit for best results.`,
|
||||
});
|
||||
|
||||
export function getWorkflowErrors(doc: Workflow): CodedError[] {
|
||||
/**
|
||||
* Groups the given list of CodeQL languages by their extractor name.
|
||||
*
|
||||
* Resolves to `undefined` if the CodeQL version does not support language aliasing.
|
||||
*/
|
||||
async function groupLanguagesByExtractor(
|
||||
languages: string[],
|
||||
codeql: CodeQL,
|
||||
): Promise<{ [extractorName: string]: string[] } | undefined> {
|
||||
const resolveResult = await codeql.betterResolveLanguages();
|
||||
if (!resolveResult.aliases) {
|
||||
return undefined;
|
||||
}
|
||||
const aliases = resolveResult.aliases;
|
||||
const languagesByExtractor: {
|
||||
[extractorName: string]: string[];
|
||||
} = {};
|
||||
for (const language of languages) {
|
||||
const extractorName = aliases[language] || language;
|
||||
if (!languagesByExtractor[extractorName]) {
|
||||
languagesByExtractor[extractorName] = [];
|
||||
}
|
||||
languagesByExtractor[extractorName].push(language);
|
||||
}
|
||||
return languagesByExtractor;
|
||||
}
|
||||
|
||||
export async function getWorkflowErrors(
|
||||
doc: Workflow,
|
||||
codeql: CodeQL,
|
||||
): Promise<CodedError[]> {
|
||||
const errors: CodedError[] = [];
|
||||
|
||||
const jobName = process.env.GITHUB_JOB;
|
||||
|
|
@ -112,6 +144,38 @@ export function getWorkflowErrors(doc: Workflow): CodedError[] {
|
|||
if (jobName) {
|
||||
const job = doc?.jobs?.[jobName];
|
||||
|
||||
if (job?.strategy?.matrix?.language) {
|
||||
const matrixLanguages = job.strategy.matrix.language;
|
||||
if (Array.isArray(matrixLanguages)) {
|
||||
// Map extractors to entries in the `language` matrix parameter. This will allow us to
|
||||
// detect languages which are analyzed in more than one job.
|
||||
const matrixLanguagesByExtractor = await groupLanguagesByExtractor(
|
||||
matrixLanguages,
|
||||
codeql,
|
||||
);
|
||||
// If the CodeQL version does not support language aliasing, then `matrixLanguagesByExtractor`
|
||||
// will be `undefined`. In this case, we cannot detect duplicate languages in the matrix.
|
||||
if (matrixLanguagesByExtractor !== undefined) {
|
||||
// Check for duplicate languages in the matrix
|
||||
for (const [extractor, languages] of Object.entries(
|
||||
matrixLanguagesByExtractor,
|
||||
)) {
|
||||
if (languages.length > 1) {
|
||||
errors.push({
|
||||
message:
|
||||
`CodeQL language '${extractor}' is referenced by more than one entry in the ` +
|
||||
`'language' matrix parameter for job '${jobName}'. This may result in duplicate alerts. ` +
|
||||
`Please edit the 'language' matrix parameter to keep only one of the following: ${languages
|
||||
.map((language) => `'${language}'`)
|
||||
.join(", ")}.`,
|
||||
code: "DuplicateLanguageInMatrix",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const steps = job?.steps;
|
||||
|
||||
if (Array.isArray(steps)) {
|
||||
|
|
@ -163,6 +227,7 @@ export function getWorkflowErrors(doc: Workflow): CodedError[] {
|
|||
}
|
||||
|
||||
export async function validateWorkflow(
|
||||
codeql: CodeQL,
|
||||
logger: Logger,
|
||||
): Promise<undefined | string> {
|
||||
let workflow: Workflow;
|
||||
|
|
@ -173,7 +238,7 @@ export async function validateWorkflow(
|
|||
}
|
||||
let workflowErrors: CodedError[];
|
||||
try {
|
||||
workflowErrors = getWorkflowErrors(workflow);
|
||||
workflowErrors = await getWorkflowErrors(workflow, codeql);
|
||||
} catch (e) {
|
||||
return `error: getWorkflowErrors() failed: ${String(e)}`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue