mirror of
https://github.com/github/codeql-action.git
synced 2026-01-04 05:30:16 +08:00
Allow feature flags to specify tool feature requirements
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
setupActionsVars,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
import { ToolsFeature } from "./tools-features";
|
||||
import * as util from "./util";
|
||||
import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util";
|
||||
|
||||
@@ -197,7 +198,10 @@ for (const feature of Object.keys(featureConfig)) {
|
||||
});
|
||||
});
|
||||
|
||||
if (featureConfig[feature].minimumVersion !== undefined) {
|
||||
if (
|
||||
featureConfig[feature].minimumVersion !== undefined ||
|
||||
featureConfig[feature].toolsFeature !== undefined
|
||||
) {
|
||||
test(`Getting feature '${feature} should throw if no codeql is provided`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
@@ -206,7 +210,11 @@ for (const feature of Object.keys(featureConfig)) {
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
await t.throwsAsync(async () => features.getValue(feature as Feature), {
|
||||
message: `Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.`,
|
||||
message: `Internal error: A ${
|
||||
featureConfig[feature].minimumVersion !== undefined
|
||||
? "minimum version"
|
||||
: "required tools feature"
|
||||
} is specified for feature ${feature}, but no instance of CodeQL was provided.`,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -243,11 +251,44 @@ for (const feature of Object.keys(featureConfig)) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (featureConfig[feature].toolsFeature !== undefined) {
|
||||
test(`Feature '${feature}' is disabled if the required tools feature is not enabled`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
// feature should be disabled when the required tools feature is not enabled
|
||||
let codeql = mockCodeQLVersion("2.0.0");
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
|
||||
// even setting the env var to true should not enable the feature if
|
||||
// the required tools feature is not enabled
|
||||
process.env[featureConfig[feature].envVar] = "true";
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
|
||||
// feature should be enabled when the required tools feature is enabled
|
||||
// and env var is not set
|
||||
process.env[featureConfig[feature].envVar] = "";
|
||||
codeql = mockCodeQLVersion("2.0.0", {
|
||||
[featureConfig[feature].toolsFeature]: true,
|
||||
});
|
||||
t.assert(await features.getValue(feature as Feature, codeql));
|
||||
|
||||
// set env var to false and check that the feature is now disabled
|
||||
process.env[featureConfig[feature].envVar] = "false";
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If we ever run into a situation where we no longer have any features that
|
||||
// specify a minimum version, then we will have a bunch of code no longer being
|
||||
// tested. This is unlikely, and this test will fail if that happens.
|
||||
// specify a minimum version or required tools feature, then we will have a
|
||||
// bunch of code no longer being tested. This is unlikely, and this test will
|
||||
// fail if that happens.
|
||||
// If we do end up in that situation, then we should consider adding a synthetic
|
||||
// feature with a minimum version that is only used for tests.
|
||||
test("At least one feature has a minimum version specified", (t) => {
|
||||
@@ -256,6 +297,11 @@ test("At least one feature has a minimum version specified", (t) => {
|
||||
"At least one feature should have a minimum version specified",
|
||||
);
|
||||
|
||||
t.assert(
|
||||
Object.values(featureConfig).some((f) => f.toolsFeature !== undefined),
|
||||
"At least one feature should have a required tools feature specified",
|
||||
);
|
||||
|
||||
// An even less likely scenario is that we no longer have any features.
|
||||
t.assert(
|
||||
Object.values(featureConfig).length > 0,
|
||||
@@ -512,8 +558,16 @@ function setUpFeatureFlagTests(
|
||||
return new Features(gitHubVersion, testRepositoryNwo, tmpDir, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an argument to pass to `getValue` that if required includes a CodeQL object meeting the
|
||||
* minimum version or tool feature requirements specified by the feature.
|
||||
*/
|
||||
function includeCodeQlIfRequired(feature: string) {
|
||||
return featureConfig[feature].minimumVersion !== undefined
|
||||
? mockCodeQLVersion("9.9.9")
|
||||
return featureConfig[feature].minimumVersion !== undefined ||
|
||||
featureConfig[feature].toolsFeature !== undefined
|
||||
? mockCodeQLVersion(
|
||||
"9.9.9",
|
||||
Object.fromEntries(Object.values(ToolsFeature).map((v) => [v, true])),
|
||||
)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { CodeQL } from "./codeql";
|
||||
import * as defaults from "./defaults.json";
|
||||
import { Logger } from "./logging";
|
||||
import { RepositoryNwo } from "./repository";
|
||||
import { ToolsFeature } from "./tools-features";
|
||||
import * as util from "./util";
|
||||
|
||||
const DEFAULT_VERSION_FEATURE_FLAG_PREFIX = "default_codeql_version_";
|
||||
@@ -44,6 +45,7 @@ export interface FeatureEnablement {
|
||||
* Each value of this enum should end with `_enabled`.
|
||||
*/
|
||||
export enum Feature {
|
||||
AutobuildDirectTracingEnabled = "autobuild_direct_tracing_enabled",
|
||||
CliSarifMerge = "cli_sarif_merge_enabled",
|
||||
CppDependencyInstallation = "cpp_dependency_installation_enabled",
|
||||
CppTrapCachingEnabled = "cpp_trap_caching_enabled",
|
||||
@@ -55,8 +57,34 @@ export enum Feature {
|
||||
|
||||
export const featureConfig: Record<
|
||||
Feature,
|
||||
{ envVar: string; minimumVersion: string | undefined; defaultValue: boolean }
|
||||
{
|
||||
/**
|
||||
* Environment variable for explicitly enabling or disabling the feature.
|
||||
*
|
||||
* This overrides enablement status from the feature flags API.
|
||||
*/
|
||||
envVar: string;
|
||||
/**
|
||||
* Minimum version of the CLI, if applicable.
|
||||
*
|
||||
* Prefer using `ToolsFeature`s for future flags.
|
||||
*/
|
||||
minimumVersion: string | undefined;
|
||||
/** Required tools feature, if applicable. */
|
||||
toolsFeature?: ToolsFeature;
|
||||
/**
|
||||
* Default value in environments where the feature flags API is not available,
|
||||
* such as GitHub Enterprise Server.
|
||||
*/
|
||||
defaultValue: boolean;
|
||||
}
|
||||
> = {
|
||||
[Feature.AutobuildDirectTracingEnabled]: {
|
||||
envVar: "CODEQL_ACTION_AUTOBUILD_BUILD_MODE_DIRECT_TRACING",
|
||||
minimumVersion: undefined,
|
||||
toolsFeature: ToolsFeature.TraceCommandUseBuildMode,
|
||||
defaultValue: false,
|
||||
},
|
||||
[Feature.CliSarifMerge]: {
|
||||
envVar: "CODEQL_ACTION_CLI_SARIF_MERGE",
|
||||
// This is guarded by a `supportsFeature` check rather than by a version check.
|
||||
@@ -151,6 +179,11 @@ export class Features implements FeatureEnablement {
|
||||
`Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.`,
|
||||
);
|
||||
}
|
||||
if (!codeql && featureConfig[feature].toolsFeature) {
|
||||
throw new Error(
|
||||
`Internal error: A required tools feature is specified for feature ${feature}, but no instance of CodeQL was provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
const envVar = (
|
||||
process.env[featureConfig[feature].envVar] || ""
|
||||
@@ -182,6 +215,22 @@ export class Features implements FeatureEnablement {
|
||||
);
|
||||
}
|
||||
}
|
||||
const toolsFeature = featureConfig[feature].toolsFeature;
|
||||
if (codeql && toolsFeature) {
|
||||
if (!(await codeql.supportsFeature(toolsFeature))) {
|
||||
this.logger.debug(
|
||||
`Feature ${feature} is disabled because the CodeQL CLI version does not support the ` +
|
||||
`required tools feature ${toolsFeature}.`,
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
this.logger.debug(
|
||||
`CodeQL CLI version ${
|
||||
(await codeql.getVersion()).version
|
||||
} supports the required tools feature ${toolsFeature} for feature ${feature}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Use this feature if user explicitly enables it via an environment variable.
|
||||
if (envVar === "true") {
|
||||
|
||||
Reference in New Issue
Block a user