Support determining Dotcom CLI version from feature flags

This commit is contained in:
Henry Mercer 2023-01-05 13:11:53 +00:00
parent 6ba0a36550
commit cdb90196f2
12 changed files with 307 additions and 7 deletions

View file

@ -1,8 +1,9 @@
import * as fs from "fs";
import * as path from "path";
import test from "ava";
import test, { ExecutionContext } from "ava";
import * as defaults from "./defaults.json"; // Referenced from codeql-action-sync-tool!
import {
Feature,
featureConfig,
@ -371,7 +372,61 @@ test("Environment variable can override feature flag cache", async (t) => {
});
});
function assertAllFeaturesUndefinedInApi(t, loggedMessages: LoggedMessage[]) {
for (const variant of [GitHubVariant.GHAE, GitHubVariant.GHES]) {
test(`selects CLI from defaults.json on ${GitHubVariant[variant]}`, async (t) => {
await withTmpDir(async (tmpDir) => {
const features = setUpFeatureFlagTests(tmpDir);
t.deepEqual(await features.getDefaultCliVersion(variant), {
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
variant,
});
});
});
}
test("selects CLI v2.12.1 on Dotcom when feature flags enable v2.12.0 and v2.12.1", async (t) => {
await withTmpDir(async (tmpDir) => {
const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true);
expectedFeatureEnablement["default_codeql_version_2_12_0_enabled"] = true;
expectedFeatureEnablement["default_codeql_version_2_12_1_enabled"] = true;
expectedFeatureEnablement["default_codeql_version_2_12_2_enabled"] = false;
expectedFeatureEnablement["default_codeql_version_2_12_3_enabled"] = false;
expectedFeatureEnablement["default_codeql_version_2_12_4_enabled"] = false;
expectedFeatureEnablement["default_codeql_version_2_12_5_enabled"] = false;
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
t.deepEqual(
await featureEnablement.getDefaultCliVersion(GitHubVariant.DOTCOM),
{
cliVersion: "2.12.1",
variant: GitHubVariant.DOTCOM,
}
);
});
});
test(`selects CLI v2.11.6 on Dotcom when no default version feature flags are enabled`, async (t) => {
await withTmpDir(async (tmpDir) => {
const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true);
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
t.deepEqual(
await featureEnablement.getDefaultCliVersion(GitHubVariant.DOTCOM),
{
cliVersion: "2.11.6",
variant: GitHubVariant.DOTCOM,
}
);
});
});
function assertAllFeaturesUndefinedInApi(
t: ExecutionContext<unknown>,
loggedMessages: LoggedMessage[]
) {
for (const feature of Object.keys(featureConfig)) {
t.assert(
loggedMessages.find(

View file

@ -3,11 +3,31 @@ import * as path from "path";
import { getApiClient } from "./api-client";
import { CodeQL } from "./codeql";
import * as defaults from "./defaults.json"; // Referenced from codeql-action-sync-tool!
import { Logger } from "./logging";
import { RepositoryNwo } from "./repository";
import * as util from "./util";
const DEFAULT_VERSION_FEATURE_FLAG_PREFIX = "default_codeql_version_";
const DEFAULT_VERSION_FEATURE_FLAG_SUFFIX = "_enabled";
const MINIMUM_ENABLED_CODEQL_VERSION = "2.11.6";
export type CodeQLDefaultVersionInfo =
| {
cliVersion: string;
variant: util.GitHubVariant.DOTCOM;
}
| {
cliVersion: string;
tagName: string;
variant: util.GitHubVariant.GHAE | util.GitHubVariant.GHES;
};
export interface FeatureEnablement {
/** Gets the default version of the CodeQL tools. */
getDefaultCliVersion(
variant: util.GitHubVariant
): Promise<CodeQLDefaultVersionInfo>;
getValue(feature: Feature, codeql?: CodeQL): Promise<boolean>;
}
@ -91,6 +111,12 @@ export class Features implements FeatureEnablement {
);
}
async getDefaultCliVersion(
variant: util.GitHubVariant
): Promise<CodeQLDefaultVersionInfo> {
return await this.gitHubFeatureFlags.getDefaultCliVersion(variant);
}
/**
*
* @param feature The feature to check.
@ -153,6 +179,68 @@ class GitHubFeatureFlags implements FeatureEnablement {
/**/
}
private static getCliVersionFromFeatureFlag(f: string): string | undefined {
if (
!f.startsWith(DEFAULT_VERSION_FEATURE_FLAG_PREFIX) ||
!f.endsWith(DEFAULT_VERSION_FEATURE_FLAG_SUFFIX)
) {
return undefined;
}
return f
.substring(
DEFAULT_VERSION_FEATURE_FLAG_PREFIX.length,
f.length - DEFAULT_VERSION_FEATURE_FLAG_SUFFIX.length
)
.replace(/_/g, ".");
}
async getDefaultCliVersion(
variant: util.GitHubVariant
): Promise<CodeQLDefaultVersionInfo> {
if (variant === util.GitHubVariant.DOTCOM) {
return {
cliVersion: await this.getDefaultDotcomCliVersion(),
variant,
};
}
return {
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
variant,
};
}
async getDefaultDotcomCliVersion(): Promise<string> {
const response = await this.getAllFeatures();
const enabledFeatureFlagCliVersions = Object.entries(response)
.map(([f, isEnabled]) =>
isEnabled
? GitHubFeatureFlags.getCliVersionFromFeatureFlag(f)
: undefined
)
.filter((f) => f !== undefined)
.map((f) => f as string);
if (enabledFeatureFlagCliVersions.length === 0) {
this.logger.debug(
"Feature flags do not specify a default CLI version. Falling back to CLI version " +
`${MINIMUM_ENABLED_CODEQL_VERSION}.`
);
return MINIMUM_ENABLED_CODEQL_VERSION;
}
const maxCliVersion = enabledFeatureFlagCliVersions.reduce(
(maxVersion, currentVersion) =>
currentVersion > maxVersion ? currentVersion : maxVersion,
enabledFeatureFlagCliVersions[0]
);
this.logger.debug(
`Derived default CLI version of ${maxCliVersion} from feature flags.`
);
return maxCliVersion;
}
async getValue(feature: Feature): Promise<boolean> {
const response = await this.getAllFeatures();
if (response === undefined) {

View file

@ -201,6 +201,9 @@ export function mockCodeQLVersion(version) {
*/
export function createFeatures(enabledFeatures: Feature[]): FeatureEnablement {
return {
getDefaultCliVersion: async () => {
throw new Error("not implemented");
},
getValue: async (feature) => {
return enabledFeatures.includes(feature);
},

View file

@ -19,6 +19,7 @@ import {
parsePacksSpecification,
prettyPrintPack,
} from "./config-utils";
import * as defaults from "./defaults.json"; // Referenced from codeql-action-sync-tool!
import { Feature, FeatureEnablement } from "./feature-flags";
import { KOTLIN_SWIFT_BYPASS, Language } from "./languages";
import { Logger } from "./logging";
@ -901,3 +902,44 @@ export function parseMatrixInput(
}
return JSON.parse(matrixInput);
}
/** Version information about a CodeQL bundle. */
export interface CodeqlBundleVersionInfo {
/** Version number of the CLI contained within this bundle. */
cliVersion: string;
/** The name of the tag of the GitHub Release containing this bundle. */
tagName: string;
/**
* Version number of this bundle within the toolcache.
*
* For compatibility with previous runner images and toolcaches, consumers should fallback to
* looking up the `0.0.0-pre` version number within the toolcache if this version number is not
* found, where `pre` is the prerelease component of this version number.
*/
toolcacheVersion: string;
}
function computeToolcacheVersion(cliVersion: string, tagName: string): string {
return `${cliVersion}-${tagName}`;
}
/**
* Gets version information about the CodeQL bundle which was current as of the release of this
* Action.
*
* This should be used in the following circumstances:
*
* - When running the Action on Enterprise instances.
* - When a user requests `tools: latest`.
* - When the Action is running on Dotcom and no default CodeQL version feature flags are enabled.
*/
export function getPinnedCodeqlVersion(): CodeqlBundleVersionInfo {
return {
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
toolcacheVersion: computeToolcacheVersion(
defaults.cliVersion,
defaults.bundleVersion
),
};
}