Merge pull request #1384 from github/aeisenberg/feature-flags-disk
Cache feature flags on disk
This commit is contained in:
commit
26df9a9a4f
13 changed files with 299 additions and 50 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## [UNRELEASED]
|
||||
|
||||
No user facing changes.
|
||||
- Fixed a bug where some the `init` action and the `analyze` action would have different sets of experimental feature flags enabled. [#1384](https://github.com/github/codeql-action/pull/1384)
|
||||
|
||||
## 2.1.33 - 16 Nov 2022
|
||||
|
||||
|
|
|
|||
2
lib/analyze-action.js
generated
2
lib/analyze-action.js
generated
|
|
@ -155,7 +155,7 @@ async function run() {
|
|||
const memory = util.getMemoryFlag(actionsUtil.getOptionalInput("ram") || process.env["CODEQL_RAM"]);
|
||||
const repositoryNwo = (0, repository_1.parseRepositoryNwo)(util.getRequiredEnvParam("GITHUB_REPOSITORY"));
|
||||
const gitHubVersion = await (0, api_client_1.getGitHubVersion)();
|
||||
const features = new feature_flags_1.Features(gitHubVersion, repositoryNwo, logger);
|
||||
const features = new feature_flags_1.Features(gitHubVersion, repositoryNwo, actionsUtil.getTemporaryDirectory(), logger);
|
||||
await runAutobuildIfLegacyGoWorkflow(config, logger);
|
||||
dbCreationTimings = await (0, analyze_1.runFinalize)(outputDir, threads, memory, config, logger);
|
||||
if (actionsUtil.getRequiredInput("skip-queries") !== "true") {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
60
lib/feature-flags.js
generated
60
lib/feature-flags.js
generated
|
|
@ -19,7 +19,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Features = exports.featureConfig = exports.Feature = void 0;
|
||||
exports.Features = exports.FEATURE_FLAGS_FILE_NAME = exports.featureConfig = exports.Feature = void 0;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const api_client_1 = require("./api-client");
|
||||
const util = __importStar(require("./util"));
|
||||
var Feature;
|
||||
|
|
@ -57,14 +59,15 @@ exports.featureConfig = {
|
|||
minimumVersion: undefined,
|
||||
},
|
||||
};
|
||||
exports.FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json";
|
||||
/**
|
||||
* Determines the enablement status of a number of features.
|
||||
* If feature enablement is not able to be determined locally, a request to the
|
||||
* GitHub API is made to determine the enablement status.
|
||||
*/
|
||||
class Features {
|
||||
constructor(gitHubVersion, repositoryNwo, logger) {
|
||||
this.gitHubFeatureFlags = new GitHubFeatureFlags(gitHubVersion, repositoryNwo, logger);
|
||||
constructor(gitHubVersion, repositoryNwo, tempDir, logger) {
|
||||
this.gitHubFeatureFlags = new GitHubFeatureFlags(gitHubVersion, repositoryNwo, path.join(tempDir, exports.FEATURE_FLAGS_FILE_NAME), logger);
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
|
@ -108,14 +111,15 @@ class Features {
|
|||
}
|
||||
exports.Features = Features;
|
||||
class GitHubFeatureFlags {
|
||||
constructor(gitHubVersion, repositoryNwo, logger) {
|
||||
constructor(gitHubVersion, repositoryNwo, featureFlagsFile, logger) {
|
||||
this.gitHubVersion = gitHubVersion;
|
||||
this.repositoryNwo = repositoryNwo;
|
||||
this.featureFlagsFile = featureFlagsFile;
|
||||
this.logger = logger;
|
||||
/**/
|
||||
}
|
||||
async getValue(feature) {
|
||||
const response = await this.getApiResponse();
|
||||
const response = await this.getAllFeatures();
|
||||
if (response === undefined) {
|
||||
this.logger.debug(`No feature flags API response for ${feature}, considering it disabled.`);
|
||||
return false;
|
||||
|
|
@ -127,10 +131,48 @@ class GitHubFeatureFlags {
|
|||
}
|
||||
return !!featureEnablement;
|
||||
}
|
||||
async getApiResponse() {
|
||||
const apiResponse = this.cachedApiResponse || (await this.loadApiResponse());
|
||||
this.cachedApiResponse = apiResponse;
|
||||
return apiResponse;
|
||||
async getAllFeatures() {
|
||||
// if we have an in memory cache, use that
|
||||
if (this.cachedApiResponse !== undefined) {
|
||||
return this.cachedApiResponse;
|
||||
}
|
||||
// if a previous step has written a feature flags file to disk, use that
|
||||
const fileFlags = await this.readLocalFlags();
|
||||
if (fileFlags !== undefined) {
|
||||
this.cachedApiResponse = fileFlags;
|
||||
return fileFlags;
|
||||
}
|
||||
// if not, request flags from the server
|
||||
let remoteFlags = await this.loadApiResponse();
|
||||
if (remoteFlags === undefined) {
|
||||
remoteFlags = {};
|
||||
}
|
||||
// cache the response in memory
|
||||
this.cachedApiResponse = remoteFlags;
|
||||
// and cache them to disk so future workflow steps can use them
|
||||
await this.writeLocalFlags(remoteFlags);
|
||||
return remoteFlags;
|
||||
}
|
||||
async readLocalFlags() {
|
||||
try {
|
||||
if (fs.existsSync(this.featureFlagsFile)) {
|
||||
this.logger.debug(`Loading feature flags from ${this.featureFlagsFile}`);
|
||||
return JSON.parse(fs.readFileSync(this.featureFlagsFile, "utf8"));
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.warning(`Error reading cached feature flags file ${this.featureFlagsFile}: ${e}. Requesting from GitHub instead.`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
async writeLocalFlags(flags) {
|
||||
try {
|
||||
this.logger.debug(`Writing feature flags to ${this.featureFlagsFile}`);
|
||||
fs.writeFileSync(this.featureFlagsFile, JSON.stringify(flags));
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.warning(`Error writing cached feature flags file ${this.featureFlagsFile}: ${e}.`);
|
||||
}
|
||||
}
|
||||
async loadApiResponse() {
|
||||
// Do nothing when not running against github.com
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"feature-flags.js","sourceRoot":"","sources":["../src/feature-flags.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"feature-flags.js","sourceRoot":"","sources":["../src/feature-flags.ts"],"names":[],"mappings":"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"}
|
||||
74
lib/feature-flags.test.js
generated
74
lib/feature-flags.test.js
generated
|
|
@ -1,8 +1,29 @@
|
|||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const logging_1 = require("./logging");
|
||||
|
|
@ -25,7 +46,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
|
|||
(0, ava_1.default)(`All features are disabled if running against ${variant.description}`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const featureEnablement = setUpTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages), variant.gitHubVersion);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages), variant.gitHubVersion);
|
||||
for (const feature of Object.values(feature_flags_1.Feature)) {
|
||||
t.false(await featureEnablement.getValue(feature, includeCodeQlIfRequired(feature)));
|
||||
}
|
||||
|
|
@ -38,7 +59,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
|
|||
(0, ava_1.default)("API response missing", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const featureEnablement = setUpTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(403, {});
|
||||
for (const feature of Object.values(feature_flags_1.Feature)) {
|
||||
t.assert((await featureEnablement.getValue(feature, includeCodeQlIfRequired(feature))) === false);
|
||||
|
|
@ -49,7 +70,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
|
|||
(0, ava_1.default)("Features are disabled if they're not returned in API response", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const featureEnablement = setUpTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, {});
|
||||
for (const feature of Object.values(feature_flags_1.Feature)) {
|
||||
t.assert((await featureEnablement.getValue(feature, includeCodeQlIfRequired(feature))) === false);
|
||||
|
|
@ -59,7 +80,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
|
|||
});
|
||||
(0, ava_1.default)("Feature flags exception is propagated if the API request errors", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(500, {});
|
||||
await t.throwsAsync(async () => featureEnablement.getValue(feature_flags_1.Feature.MlPoweredQueriesEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.MlPoweredQueriesEnabled)), {
|
||||
message: "Encountered an error while trying to determine feature enablement: Error: some error message",
|
||||
|
|
@ -69,7 +90,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
|
|||
for (const feature of Object.keys(feature_flags_1.featureConfig)) {
|
||||
(0, ava_1.default)(`Only feature '${feature}' is enabled if enabled in the API response. Other features disabled`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
// set all features to false except the one we're testing
|
||||
const expectedFeatureEnablement = {};
|
||||
for (const f of Object.keys(feature_flags_1.featureConfig)) {
|
||||
|
|
@ -87,7 +108,7 @@ for (const feature of Object.keys(feature_flags_1.featureConfig)) {
|
|||
});
|
||||
(0, ava_1.default)(`Only feature '${feature}' is enabled if the associated environment variable is true. Others disabled.`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(false);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
// feature should be disabled initially
|
||||
|
|
@ -99,7 +120,7 @@ for (const feature of Object.keys(feature_flags_1.featureConfig)) {
|
|||
});
|
||||
(0, ava_1.default)(`Feature '${feature}' is disabled if the associated environment variable is false, even if enabled in API`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
// feature should be enabled initially
|
||||
|
|
@ -112,7 +133,7 @@ for (const feature of Object.keys(feature_flags_1.featureConfig)) {
|
|||
if (feature_flags_1.featureConfig[feature].minimumVersion !== undefined) {
|
||||
(0, ava_1.default)(`Getting feature '${feature} should throw if no codeql is provided`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
await t.throwsAsync(async () => featureEnablement.getValue(feature), {
|
||||
|
|
@ -124,7 +145,7 @@ for (const feature of Object.keys(feature_flags_1.featureConfig)) {
|
|||
if (feature_flags_1.featureConfig[feature].minimumVersion !== undefined) {
|
||||
(0, ava_1.default)(`Feature '${feature}' is disabled if the minimum CLI version is below ${feature_flags_1.featureConfig[feature].minimumVersion}`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
// feature should be disabled when an old CLI version is set
|
||||
|
|
@ -156,6 +177,37 @@ for (const feature of Object.keys(feature_flags_1.featureConfig)) {
|
|||
// An even less likely scenario is that we no longer have any features.
|
||||
t.assert(Object.values(feature_flags_1.featureConfig).length > 0, "There should be at least one feature");
|
||||
});
|
||||
(0, ava_1.default)("Feature flags are saved to disk", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
const cachedFeatureFlags = path.join(tmpDir, feature_flags_1.FEATURE_FLAGS_FILE_NAME);
|
||||
t.false(fs.existsSync(cachedFeatureFlags), "Feature flag cached file should not exist before getting feature flags");
|
||||
t.true(await featureEnablement.getValue(feature_flags_1.Feature.CliConfigFileEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.CliConfigFileEnabled)), "Feature flag should be enabled initially");
|
||||
t.true(fs.existsSync(cachedFeatureFlags), "Feature flag cached file should exist after getting feature flags");
|
||||
const actualFeatureEnablement = JSON.parse(fs.readFileSync(cachedFeatureFlags, "utf8"));
|
||||
t.deepEqual(actualFeatureEnablement, expectedFeatureEnablement);
|
||||
// now test that we actually use the feature flag cache instead of the server
|
||||
actualFeatureEnablement[feature_flags_1.Feature.CliConfigFileEnabled] = false;
|
||||
fs.writeFileSync(cachedFeatureFlags, JSON.stringify(actualFeatureEnablement));
|
||||
// delete the in memory cache so that we are forced to use the cached file
|
||||
featureEnablement.gitHubFeatureFlags.cachedApiResponse = undefined;
|
||||
t.false(await featureEnablement.getValue(feature_flags_1.Feature.CliConfigFileEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.CliConfigFileEnabled)), "Feature flag should be enabled after reading from cached file");
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("Environment variable can override feature flag cache", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
const cachedFeatureFlags = path.join(tmpDir, feature_flags_1.FEATURE_FLAGS_FILE_NAME);
|
||||
t.true(await featureEnablement.getValue(feature_flags_1.Feature.CliConfigFileEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.CliConfigFileEnabled)), "Feature flag should be enabled initially");
|
||||
t.true(fs.existsSync(cachedFeatureFlags), "Feature flag cached file should exist after getting feature flags");
|
||||
process.env.CODEQL_PASS_CONFIG_TO_CLI = "false";
|
||||
t.false(await featureEnablement.getValue(feature_flags_1.Feature.CliConfigFileEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.CliConfigFileEnabled)), "Feature flag should be disabled after setting env var");
|
||||
});
|
||||
});
|
||||
function assertAllFeaturesUndefinedInApi(t, loggedMessages) {
|
||||
for (const feature of Object.keys(feature_flags_1.featureConfig)) {
|
||||
t.assert(loggedMessages.find((v) => v.type === "debug" &&
|
||||
|
|
@ -169,9 +221,9 @@ function initializeFeatures(initialValue) {
|
|||
return features;
|
||||
}, {});
|
||||
}
|
||||
function setUpTests(tmpDir, logger = (0, logging_1.getRunnerLogger)(true), gitHubVersion = { type: util_1.GitHubVariant.DOTCOM }) {
|
||||
function setUpFeatureFlagTests(tmpDir, logger = (0, logging_1.getRunnerLogger)(true), gitHubVersion = { type: util_1.GitHubVariant.DOTCOM }) {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
return new feature_flags_1.Features(gitHubVersion, testRepositoryNwo, logger);
|
||||
return new feature_flags_1.Features(gitHubVersion, testRepositoryNwo, tmpDir, logger);
|
||||
}
|
||||
function includeCodeQlIfRequired(feature) {
|
||||
return feature_flags_1.featureConfig[feature].minimumVersion !== undefined
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
2
lib/init-action.js
generated
2
lib/init-action.js
generated
|
|
@ -88,7 +88,7 @@ async function run() {
|
|||
const gitHubVersion = await (0, api_client_1.getGitHubVersion)();
|
||||
(0, util_1.checkGitHubVersionInRange)(gitHubVersion, logger);
|
||||
const repositoryNwo = (0, repository_1.parseRepositoryNwo)((0, util_1.getRequiredEnvParam)("GITHUB_REPOSITORY"));
|
||||
const features = new feature_flags_1.Features(gitHubVersion, repositoryNwo, logger);
|
||||
const features = new feature_flags_1.Features(gitHubVersion, repositoryNwo, (0, actions_util_1.getTemporaryDirectory)(), logger);
|
||||
try {
|
||||
const workflowErrors = await (0, actions_util_1.validateWorkflow)();
|
||||
if (!(await (0, actions_util_1.sendStatusReport)(await (0, actions_util_1.createStatusReportBase)("init", "starting", startedAt, workflowErrors)))) {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -217,7 +217,12 @@ async function run() {
|
|||
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
|
||||
const features = new Features(gitHubVersion, repositoryNwo, logger);
|
||||
const features = new Features(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
actionsUtil.getTemporaryDirectory(),
|
||||
logger
|
||||
);
|
||||
|
||||
await runAutobuildIfLegacyGoWorkflow(config, logger);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import test from "ava";
|
||||
|
||||
import {
|
||||
|
|
@ -5,6 +8,7 @@ import {
|
|||
featureConfig,
|
||||
FeatureEnablement,
|
||||
Features,
|
||||
FEATURE_FLAGS_FILE_NAME,
|
||||
} from "./feature-flags";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { parseRepositoryNwo } from "./repository";
|
||||
|
|
@ -42,7 +46,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
|
|||
test(`All features are disabled if running against ${variant.description}`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const featureEnablement = setUpTests(
|
||||
const featureEnablement = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
variant.gitHubVersion
|
||||
|
|
@ -72,7 +76,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
|
|||
test("API response missing", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const featureEnablement = setUpTests(
|
||||
const featureEnablement = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages)
|
||||
);
|
||||
|
|
@ -94,7 +98,7 @@ test("API response missing", async (t) => {
|
|||
test("Features are disabled if they're not returned in API response", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const featureEnablement = setUpTests(
|
||||
const featureEnablement = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages)
|
||||
);
|
||||
|
|
@ -116,7 +120,7 @@ test("Features are disabled if they're not returned in API response", async (t)
|
|||
|
||||
test("Feature flags exception is propagated if the API request errors", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
mockFeatureFlagApiEndpoint(500, {});
|
||||
|
||||
|
|
@ -137,7 +141,7 @@ test("Feature flags exception is propagated if the API request errors", async (t
|
|||
for (const feature of Object.keys(featureConfig)) {
|
||||
test(`Only feature '${feature}' is enabled if enabled in the API response. Other features disabled`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
// set all features to false except the one we're testing
|
||||
const expectedFeatureEnablement: { [feature: string]: boolean } = {};
|
||||
|
|
@ -162,7 +166,7 @@ for (const feature of Object.keys(featureConfig)) {
|
|||
|
||||
test(`Only feature '${feature}' is enabled if the associated environment variable is true. Others disabled.`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(false);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
|
@ -188,7 +192,7 @@ for (const feature of Object.keys(featureConfig)) {
|
|||
|
||||
test(`Feature '${feature}' is disabled if the associated environment variable is false, even if enabled in API`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
|
@ -215,7 +219,7 @@ for (const feature of Object.keys(featureConfig)) {
|
|||
if (featureConfig[feature].minimumVersion !== undefined) {
|
||||
test(`Getting feature '${feature} should throw if no codeql is provided`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
|
@ -233,7 +237,7 @@ for (const feature of Object.keys(featureConfig)) {
|
|||
if (featureConfig[feature].minimumVersion !== undefined) {
|
||||
test(`Feature '${feature}' is disabled if the minimum CLI version is below ${featureConfig[feature].minimumVersion}`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const featureEnablement = setUpTests(tmpDir);
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
|
@ -285,6 +289,88 @@ test("At least one feature has a minimum version specified", (t) => {
|
|||
);
|
||||
});
|
||||
|
||||
test("Feature flags are saved to disk", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
const cachedFeatureFlags = path.join(tmpDir, FEATURE_FLAGS_FILE_NAME);
|
||||
|
||||
t.false(
|
||||
fs.existsSync(cachedFeatureFlags),
|
||||
"Feature flag cached file should not exist before getting feature flags"
|
||||
);
|
||||
|
||||
t.true(
|
||||
await featureEnablement.getValue(
|
||||
Feature.CliConfigFileEnabled,
|
||||
includeCodeQlIfRequired(Feature.CliConfigFileEnabled)
|
||||
),
|
||||
"Feature flag should be enabled initially"
|
||||
);
|
||||
|
||||
t.true(
|
||||
fs.existsSync(cachedFeatureFlags),
|
||||
"Feature flag cached file should exist after getting feature flags"
|
||||
);
|
||||
|
||||
const actualFeatureEnablement = JSON.parse(
|
||||
fs.readFileSync(cachedFeatureFlags, "utf8")
|
||||
);
|
||||
t.deepEqual(actualFeatureEnablement, expectedFeatureEnablement);
|
||||
|
||||
// now test that we actually use the feature flag cache instead of the server
|
||||
actualFeatureEnablement[Feature.CliConfigFileEnabled] = false;
|
||||
fs.writeFileSync(
|
||||
cachedFeatureFlags,
|
||||
JSON.stringify(actualFeatureEnablement)
|
||||
);
|
||||
|
||||
// delete the in memory cache so that we are forced to use the cached file
|
||||
(featureEnablement as any).gitHubFeatureFlags.cachedApiResponse = undefined;
|
||||
|
||||
t.false(
|
||||
await featureEnablement.getValue(
|
||||
Feature.CliConfigFileEnabled,
|
||||
includeCodeQlIfRequired(Feature.CliConfigFileEnabled)
|
||||
),
|
||||
"Feature flag should be enabled after reading from cached file"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("Environment variable can override feature flag cache", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const featureEnablement = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
const cachedFeatureFlags = path.join(tmpDir, FEATURE_FLAGS_FILE_NAME);
|
||||
t.true(
|
||||
await featureEnablement.getValue(
|
||||
Feature.CliConfigFileEnabled,
|
||||
includeCodeQlIfRequired(Feature.CliConfigFileEnabled)
|
||||
),
|
||||
"Feature flag should be enabled initially"
|
||||
);
|
||||
|
||||
t.true(
|
||||
fs.existsSync(cachedFeatureFlags),
|
||||
"Feature flag cached file should exist after getting feature flags"
|
||||
);
|
||||
process.env.CODEQL_PASS_CONFIG_TO_CLI = "false";
|
||||
|
||||
t.false(
|
||||
await featureEnablement.getValue(
|
||||
Feature.CliConfigFileEnabled,
|
||||
includeCodeQlIfRequired(Feature.CliConfigFileEnabled)
|
||||
),
|
||||
"Feature flag should be disabled after setting env var"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function assertAllFeaturesUndefinedInApi(t, loggedMessages: LoggedMessage[]) {
|
||||
for (const feature of Object.keys(featureConfig)) {
|
||||
t.assert(
|
||||
|
|
@ -305,14 +391,14 @@ function initializeFeatures(initialValue: boolean) {
|
|||
}, {});
|
||||
}
|
||||
|
||||
function setUpTests(
|
||||
function setUpFeatureFlagTests(
|
||||
tmpDir: string,
|
||||
logger = getRunnerLogger(true),
|
||||
gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion
|
||||
): FeatureEnablement {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
return new Features(gitHubVersion, testRepositoryNwo, logger);
|
||||
return new Features(gitHubVersion, testRepositoryNwo, tmpDir, logger);
|
||||
}
|
||||
|
||||
function includeCodeQlIfRequired(feature: string) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import { getApiClient } from "./api-client";
|
||||
import { CodeQL } from "./codeql";
|
||||
import { Logger } from "./logging";
|
||||
|
|
@ -55,6 +58,8 @@ export const featureConfig: Record<
|
|||
*/
|
||||
type GitHubFeatureFlagsApiResponse = Partial<Record<Feature, boolean>>;
|
||||
|
||||
export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json";
|
||||
|
||||
/**
|
||||
* Determines the enablement status of a number of features.
|
||||
* If feature enablement is not able to be determined locally, a request to the
|
||||
|
|
@ -66,11 +71,13 @@ export class Features implements FeatureEnablement {
|
|||
constructor(
|
||||
gitHubVersion: util.GitHubVersion,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
tempDir: string,
|
||||
logger: Logger
|
||||
) {
|
||||
this.gitHubFeatureFlags = new GitHubFeatureFlags(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
path.join(tempDir, FEATURE_FLAGS_FILE_NAME),
|
||||
logger
|
||||
);
|
||||
}
|
||||
|
|
@ -120,7 +127,6 @@ export class Features implements FeatureEnablement {
|
|||
if (envVar === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ask the GitHub API if the feature is enabled.
|
||||
return await this.gitHubFeatureFlags.getValue(feature);
|
||||
}
|
||||
|
|
@ -130,15 +136,16 @@ class GitHubFeatureFlags implements FeatureEnablement {
|
|||
private cachedApiResponse: GitHubFeatureFlagsApiResponse | undefined;
|
||||
|
||||
constructor(
|
||||
private gitHubVersion: util.GitHubVersion,
|
||||
private repositoryNwo: RepositoryNwo,
|
||||
private logger: Logger
|
||||
private readonly gitHubVersion: util.GitHubVersion,
|
||||
private readonly repositoryNwo: RepositoryNwo,
|
||||
private readonly featureFlagsFile: string,
|
||||
private readonly logger: Logger
|
||||
) {
|
||||
/**/
|
||||
}
|
||||
|
||||
async getValue(feature: Feature): Promise<boolean> {
|
||||
const response = await this.getApiResponse();
|
||||
const response = await this.getAllFeatures();
|
||||
if (response === undefined) {
|
||||
this.logger.debug(
|
||||
`No feature flags API response for ${feature}, considering it disabled.`
|
||||
|
|
@ -155,11 +162,63 @@ class GitHubFeatureFlags implements FeatureEnablement {
|
|||
return !!featureEnablement;
|
||||
}
|
||||
|
||||
private async getApiResponse(): Promise<GitHubFeatureFlagsApiResponse> {
|
||||
const apiResponse =
|
||||
this.cachedApiResponse || (await this.loadApiResponse());
|
||||
this.cachedApiResponse = apiResponse;
|
||||
return apiResponse;
|
||||
private async getAllFeatures(): Promise<GitHubFeatureFlagsApiResponse> {
|
||||
// if we have an in memory cache, use that
|
||||
if (this.cachedApiResponse !== undefined) {
|
||||
return this.cachedApiResponse;
|
||||
}
|
||||
|
||||
// if a previous step has written a feature flags file to disk, use that
|
||||
const fileFlags = await this.readLocalFlags();
|
||||
if (fileFlags !== undefined) {
|
||||
this.cachedApiResponse = fileFlags;
|
||||
return fileFlags;
|
||||
}
|
||||
|
||||
// if not, request flags from the server
|
||||
let remoteFlags = await this.loadApiResponse();
|
||||
if (remoteFlags === undefined) {
|
||||
remoteFlags = {};
|
||||
}
|
||||
|
||||
// cache the response in memory
|
||||
this.cachedApiResponse = remoteFlags;
|
||||
|
||||
// and cache them to disk so future workflow steps can use them
|
||||
await this.writeLocalFlags(remoteFlags);
|
||||
|
||||
return remoteFlags;
|
||||
}
|
||||
|
||||
private async readLocalFlags(): Promise<
|
||||
GitHubFeatureFlagsApiResponse | undefined
|
||||
> {
|
||||
try {
|
||||
if (fs.existsSync(this.featureFlagsFile)) {
|
||||
this.logger.debug(
|
||||
`Loading feature flags from ${this.featureFlagsFile}`
|
||||
);
|
||||
return JSON.parse(fs.readFileSync(this.featureFlagsFile, "utf8"));
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.warning(
|
||||
`Error reading cached feature flags file ${this.featureFlagsFile}: ${e}. Requesting from GitHub instead.`
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async writeLocalFlags(
|
||||
flags: GitHubFeatureFlagsApiResponse
|
||||
): Promise<void> {
|
||||
try {
|
||||
this.logger.debug(`Writing feature flags to ${this.featureFlagsFile}`);
|
||||
fs.writeFileSync(this.featureFlagsFile, JSON.stringify(flags));
|
||||
} catch (e) {
|
||||
this.logger.warning(
|
||||
`Error writing cached feature flags file ${this.featureFlagsFile}: ${e}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadApiResponse() {
|
||||
|
|
|
|||
|
|
@ -158,7 +158,12 @@ async function run() {
|
|||
getRequiredEnvParam("GITHUB_REPOSITORY")
|
||||
);
|
||||
|
||||
const features = new Features(gitHubVersion, repositoryNwo, logger);
|
||||
const features = new Features(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
logger
|
||||
);
|
||||
|
||||
try {
|
||||
const workflowErrors = await validateWorkflow();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue