Extract GitHubFeatureFlags to a separate class
Internal refactoring so that `GitHubFeatureFlags` is private only. The public facing class is `Features`.
This commit is contained in:
parent
5915e70486
commit
b27aed78f5
15 changed files with 149 additions and 110 deletions
|
|
@ -19,7 +19,7 @@ import { runAutobuild } from "./autobuild";
|
|||
import { getCodeQL } from "./codeql";
|
||||
import { Config, getConfig } from "./config-utils";
|
||||
import { uploadDatabases } from "./database-upload";
|
||||
import { FeatureFlags, GitHubFeatureFlags } from "./feature-flags";
|
||||
import { FeatureFlags, Features } from "./feature-flags";
|
||||
import { Language } from "./languages";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import { parseRepositoryNwo } from "./repository";
|
||||
|
|
@ -228,7 +228,7 @@ async function run() {
|
|||
|
||||
const gitHubVersion = await getGitHubVersionActionsOnly();
|
||||
|
||||
const featureFlags = new GitHubFeatureFlags(
|
||||
const featureFlags = new Features(
|
||||
gitHubVersion,
|
||||
apiDetails,
|
||||
repositoryNwo,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
import { getApiDetails, getGitHubVersionActionsOnly } from "./api-client";
|
||||
import { determineAutobuildLanguages, runAutobuild } from "./autobuild";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { GitHubFeatureFlags } from "./feature-flags";
|
||||
import { Features } from "./feature-flags";
|
||||
import { Language } from "./languages";
|
||||
import { getActionsLogger } from "./logging";
|
||||
import { parseRepositoryNwo } from "./repository";
|
||||
|
|
@ -76,7 +76,7 @@ async function run() {
|
|||
const gitHubVersion = await getGitHubVersionActionsOnly();
|
||||
checkGitHubVersionInRange(gitHubVersion, logger, Mode.actions);
|
||||
|
||||
const featureFlags = new GitHubFeatureFlags(
|
||||
const featureFlags = new Features(
|
||||
gitHubVersion,
|
||||
getApiDetails(),
|
||||
parseRepositoryNwo(getRequiredEnvParam("GITHUB_REPOSITORY")),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
Feature,
|
||||
featureConfig,
|
||||
FeatureFlags,
|
||||
GitHubFeatureFlags,
|
||||
Features,
|
||||
} from "./feature-flags";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { parseRepositoryNwo } from "./repository";
|
||||
|
|
@ -217,7 +217,7 @@ for (const featureFlag of Object.keys(featureConfig)) {
|
|||
await t.throwsAsync(
|
||||
async () => featureFlags.getValue(featureFlag as Feature),
|
||||
{
|
||||
message: `A minimum version is specified for feature flag ${featureFlag}, but no instance of CodeQL was provided.`,
|
||||
message: `Internal error: A minimum version is specified for feature flag ${featureFlag}, but no instance of CodeQL was provided.`,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -288,12 +288,7 @@ function setUpTests(
|
|||
): FeatureFlags {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
return new GitHubFeatureFlags(
|
||||
gitHubVersion,
|
||||
testApiDetails,
|
||||
testRepositoryNwo,
|
||||
logger
|
||||
);
|
||||
return new Features(gitHubVersion, testApiDetails, testRepositoryNwo, logger);
|
||||
}
|
||||
|
||||
function includeCodeQlIfRequired(featureFlag: string) {
|
||||
|
|
|
|||
|
|
@ -50,15 +50,27 @@ export const featureConfig: Record<
|
|||
*/
|
||||
type FeatureFlagsApiResponse = Partial<Record<Feature, boolean>>;
|
||||
|
||||
export class GitHubFeatureFlags implements FeatureFlags {
|
||||
private cachedApiResponse: FeatureFlagsApiResponse | undefined;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export class Features implements FeatureFlags {
|
||||
private gitHubFeatureFlags: GitHubFeatureFlags;
|
||||
|
||||
constructor(
|
||||
private gitHubVersion: util.GitHubVersion,
|
||||
private apiDetails: GitHubApiDetails,
|
||||
private repositoryNwo: RepositoryNwo,
|
||||
private logger: Logger
|
||||
) {}
|
||||
gitHubVersion: util.GitHubVersion,
|
||||
apiDetails: GitHubApiDetails,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
logger: Logger
|
||||
) {
|
||||
this.gitHubFeatureFlags = new GitHubFeatureFlags(
|
||||
gitHubVersion,
|
||||
apiDetails,
|
||||
repositoryNwo,
|
||||
logger
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -107,6 +119,23 @@ export class GitHubFeatureFlags implements FeatureFlags {
|
|||
}
|
||||
|
||||
// Ask the GitHub API if the feature is enabled.
|
||||
return await this.gitHubFeatureFlags.getValue(flag);
|
||||
}
|
||||
}
|
||||
|
||||
class GitHubFeatureFlags implements FeatureFlags {
|
||||
private cachedApiResponse: FeatureFlagsApiResponse | undefined;
|
||||
|
||||
constructor(
|
||||
private gitHubVersion: util.GitHubVersion,
|
||||
private apiDetails: GitHubApiDetails,
|
||||
private repositoryNwo: RepositoryNwo,
|
||||
private logger: Logger
|
||||
) {
|
||||
/**/
|
||||
}
|
||||
|
||||
async getValue(flag: Feature): Promise<boolean> {
|
||||
const response = await this.getApiResponse();
|
||||
if (response === undefined) {
|
||||
this.logger.debug(
|
||||
|
|
@ -121,52 +150,53 @@ export class GitHubFeatureFlags implements FeatureFlags {
|
|||
);
|
||||
return false;
|
||||
}
|
||||
return flagValue;
|
||||
return flagValue || false;
|
||||
}
|
||||
|
||||
private async getApiResponse(): Promise<FeatureFlagsApiResponse> {
|
||||
const loadApiResponse = async () => {
|
||||
// Do nothing when not running against github.com
|
||||
if (this.gitHubVersion.type !== util.GitHubVariant.DOTCOM) {
|
||||
this.logger.debug(
|
||||
"Not running against github.com. Disabling all feature flags."
|
||||
);
|
||||
return {};
|
||||
}
|
||||
const client = getApiClient(this.apiDetails);
|
||||
try {
|
||||
const response = await client.request(
|
||||
"GET /repos/:owner/:repo/code-scanning/codeql-action/features",
|
||||
{
|
||||
owner: this.repositoryNwo.owner,
|
||||
repo: this.repositoryNwo.repo,
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
if (util.isHTTPError(e) && e.status === 403) {
|
||||
this.logger.warning(
|
||||
"This run of the CodeQL Action does not have permission to access Code Scanning API endpoints. " +
|
||||
"As a result, it will not be opted into any experimental features. " +
|
||||
"This could be because the Action is running on a pull request from a fork. If not, " +
|
||||
`please ensure the Action has the 'security-events: write' permission. Details: ${e}`
|
||||
);
|
||||
} else {
|
||||
// Some feature flags, such as `ml_powered_queries_enabled` affect the produced alerts.
|
||||
// Considering these feature flags disabled in the event of a transient error could
|
||||
// therefore lead to alert churn. As a result, we crash if we cannot determine the value of
|
||||
// the feature flags.
|
||||
throw new Error(
|
||||
`Encountered an error while trying to load feature flags: ${e}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const apiResponse = this.cachedApiResponse || (await loadApiResponse());
|
||||
const apiResponse =
|
||||
this.cachedApiResponse || (await this.loadApiResponse());
|
||||
this.cachedApiResponse = apiResponse;
|
||||
return apiResponse;
|
||||
}
|
||||
|
||||
private async loadApiResponse() {
|
||||
// Do nothing when not running against github.com
|
||||
if (this.gitHubVersion.type !== util.GitHubVariant.DOTCOM) {
|
||||
this.logger.debug(
|
||||
"Not running against github.com. Disabling all feature flags."
|
||||
);
|
||||
return {};
|
||||
}
|
||||
const client = getApiClient(this.apiDetails);
|
||||
try {
|
||||
const response = await client.request(
|
||||
"GET /repos/:owner/:repo/code-scanning/codeql-action/features",
|
||||
{
|
||||
owner: this.repositoryNwo.owner,
|
||||
repo: this.repositoryNwo.repo,
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
if (util.isHTTPError(e) && e.status === 403) {
|
||||
this.logger.warning(
|
||||
"This run of the CodeQL Action does not have permission to access Code Scanning API endpoints. " +
|
||||
"As a result, it will not be opted into any experimental features. " +
|
||||
"This could be because the Action is running on a pull request from a fork. If not, " +
|
||||
`please ensure the Action has the 'security-events: write' permission. Details: ${e}`
|
||||
);
|
||||
} else {
|
||||
// Some feature flags, such as `ml_powered_queries_enabled` affect the produced alerts.
|
||||
// Considering these feature flags disabled in the event of a transient error could
|
||||
// therefore lead to alert churn. As a result, we crash if we cannot determine the value of
|
||||
// the feature flags.
|
||||
throw new Error(
|
||||
`Encountered an error while trying to load feature flags: ${e}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
import { getGitHubVersionActionsOnly } from "./api-client";
|
||||
import { CodeQL, CODEQL_VERSION_NEW_TRACING } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { Feature, FeatureFlags, GitHubFeatureFlags } from "./feature-flags";
|
||||
import { Feature, FeatureFlags, Features } from "./feature-flags";
|
||||
import {
|
||||
initCodeQL,
|
||||
initConfig,
|
||||
|
|
@ -157,7 +157,7 @@ async function run() {
|
|||
getRequiredEnvParam("GITHUB_REPOSITORY")
|
||||
);
|
||||
|
||||
const featureFlags = new GitHubFeatureFlags(
|
||||
const featureFlags = new Features(
|
||||
gitHubVersion,
|
||||
apiDetails,
|
||||
repositoryNwo,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue