mirror of
https://github.com/github/codeql-action.git
synced 2026-05-23 06:18:42 +00:00
229 lines
7.9 KiB
TypeScript
229 lines
7.9 KiB
TypeScript
import { isDynamicWorkflow } from "../actions-util";
|
|
import { getRepositoryProperties } from "../api-client";
|
|
import { Logger } from "../logging";
|
|
import { RepositoryNwo } from "../repository";
|
|
|
|
/** The common prefix that we expect all of our repository properties to have. */
|
|
export const GITHUB_CODEQL_PROPERTY_PREFIX = "github-codeql-";
|
|
|
|
/**
|
|
* Enumerates repository property names that have some meaning to us.
|
|
*/
|
|
export enum RepositoryPropertyName {
|
|
DISABLE_OVERLAY = "github-codeql-disable-overlay",
|
|
EXTRA_QUERIES = "github-codeql-extra-queries",
|
|
FILE_COVERAGE_ON_PRS = "github-codeql-file-coverage-on-prs",
|
|
}
|
|
|
|
/** Parsed types of the known repository properties. */
|
|
export type AllRepositoryProperties = {
|
|
[RepositoryPropertyName.DISABLE_OVERLAY]: boolean;
|
|
[RepositoryPropertyName.EXTRA_QUERIES]: string;
|
|
[RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: boolean;
|
|
};
|
|
|
|
/** Parsed repository properties. */
|
|
export type RepositoryProperties = Partial<AllRepositoryProperties>;
|
|
|
|
/** Maps known repository properties to the type we expect to get from the API. */
|
|
export type RepositoryPropertyApiType = {
|
|
[RepositoryPropertyName.DISABLE_OVERLAY]: string;
|
|
[RepositoryPropertyName.EXTRA_QUERIES]: string;
|
|
[RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: string;
|
|
};
|
|
|
|
/** The type of functions which take the `value` from the API and try to convert it to the type we want. */
|
|
export type PropertyParser<K extends RepositoryPropertyName> = (
|
|
name: K,
|
|
value: RepositoryPropertyApiType[K],
|
|
logger: Logger,
|
|
) => AllRepositoryProperties[K];
|
|
|
|
/** Possible types of `value`s we get from the API. */
|
|
export type RepositoryPropertyValue = string | string[];
|
|
|
|
/** The type of repository property configurations. */
|
|
export type PropertyInfo<K extends RepositoryPropertyName> = {
|
|
/** A validator which checks that the value received from the API is what we expect. */
|
|
validate: (
|
|
value: RepositoryPropertyValue,
|
|
) => value is RepositoryPropertyApiType[K];
|
|
/** A `PropertyParser` for the property. */
|
|
parse: PropertyParser<K>;
|
|
};
|
|
|
|
/** Determines whether a value from the API is a string or not. */
|
|
function isString(value: RepositoryPropertyValue): value is string {
|
|
return typeof value === "string";
|
|
}
|
|
|
|
/** A repository property that we expect to contain a string value. */
|
|
const stringProperty = {
|
|
validate: isString,
|
|
parse: parseStringRepositoryProperty,
|
|
};
|
|
|
|
/** A repository property that we expect to contain a boolean value. */
|
|
const booleanProperty = {
|
|
// The value from the API should come as a string, which we then parse into a boolean.
|
|
validate: isString,
|
|
parse: parseBooleanRepositoryProperty,
|
|
};
|
|
|
|
/** Parsers that transform repository properties from the API response into typed values. */
|
|
const repositoryPropertyParsers: {
|
|
[K in RepositoryPropertyName]: PropertyInfo<K>;
|
|
} = {
|
|
[RepositoryPropertyName.DISABLE_OVERLAY]: booleanProperty,
|
|
[RepositoryPropertyName.EXTRA_QUERIES]: stringProperty,
|
|
[RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: booleanProperty,
|
|
};
|
|
|
|
/**
|
|
* A repository property has a name and a value.
|
|
*/
|
|
export interface GitHubRepositoryProperty {
|
|
property_name: string;
|
|
value: RepositoryPropertyValue;
|
|
}
|
|
|
|
/**
|
|
* The API returns a list of `GitHubRepositoryProperty` objects.
|
|
*/
|
|
export type GitHubPropertiesResponse = GitHubRepositoryProperty[];
|
|
|
|
/**
|
|
* Retrieves all known repository properties from the API.
|
|
*
|
|
* @param logger The logger to use.
|
|
* @param repositoryNwo Information about the repository for which to load properties.
|
|
* @returns Returns a partial mapping from `RepositoryPropertyName` to values.
|
|
*/
|
|
export async function loadPropertiesFromApi(
|
|
logger: Logger,
|
|
repositoryNwo: RepositoryNwo,
|
|
): Promise<RepositoryProperties> {
|
|
try {
|
|
const response = await getRepositoryProperties(repositoryNwo);
|
|
const remoteProperties = response.data as GitHubPropertiesResponse;
|
|
|
|
if (!Array.isArray(remoteProperties)) {
|
|
throw new Error(
|
|
`Expected repository properties API to return an array, but got: ${JSON.stringify(response.data)}`,
|
|
);
|
|
}
|
|
|
|
logger.debug(
|
|
`Retrieved ${remoteProperties.length} repository properties: ${remoteProperties.map((p) => p.property_name).join(", ")}`,
|
|
);
|
|
|
|
const properties: RepositoryProperties = {};
|
|
const unrecognisedProperties: string[] = [];
|
|
|
|
for (const property of remoteProperties) {
|
|
if (property.property_name === undefined) {
|
|
throw new Error(
|
|
`Expected repository property object to have a 'property_name', but got: ${JSON.stringify(property)}`,
|
|
);
|
|
}
|
|
|
|
if (isKnownPropertyName(property.property_name)) {
|
|
setProperty(properties, property.property_name, property.value, logger);
|
|
} else if (
|
|
property.property_name.startsWith(GITHUB_CODEQL_PROPERTY_PREFIX) &&
|
|
!isDynamicWorkflow()
|
|
) {
|
|
unrecognisedProperties.push(property.property_name);
|
|
}
|
|
}
|
|
|
|
if (Object.keys(properties).length === 0) {
|
|
logger.debug("No known repository properties were found.");
|
|
} else {
|
|
logger.debug(
|
|
"Loaded the following values for the repository properties:",
|
|
);
|
|
for (const [property, value] of Object.entries(properties).sort(
|
|
([nameA], [nameB]) => nameA.localeCompare(nameB),
|
|
)) {
|
|
logger.debug(` ${property}: ${value}`);
|
|
}
|
|
}
|
|
|
|
// Emit a warning if we encountered unrecognised properties that have our prefix.
|
|
if (unrecognisedProperties.length > 0) {
|
|
const unrecognisedPropertyList = unrecognisedProperties
|
|
.map((name) => `'${name}'`)
|
|
.join(", ");
|
|
|
|
logger.warning(
|
|
`Found repository properties (${unrecognisedPropertyList}), ` +
|
|
"which look like CodeQL Action repository properties, " +
|
|
"but which are not understood by this version of the CodeQL Action. " +
|
|
"Do you need to update to a newer version?",
|
|
);
|
|
}
|
|
|
|
return properties;
|
|
} catch (e) {
|
|
throw new Error(
|
|
`Encountered an error while trying to determine repository properties: ${e}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate that `value` has the correct type for `K` and, if so, update the partial set of repository
|
|
* properties with the parsed value of the specified property.
|
|
*/
|
|
function setProperty<K extends RepositoryPropertyName>(
|
|
properties: RepositoryProperties,
|
|
name: K,
|
|
value: RepositoryPropertyValue,
|
|
logger: Logger,
|
|
): void {
|
|
const propertyOptions = repositoryPropertyParsers[name];
|
|
|
|
// We perform the validation here for two reasons:
|
|
// 1. This function is only called if `name` is a property we care about, to avoid throwing
|
|
// on unrelated properties that may use representations we do not support.
|
|
// 2. The `propertyOptions.validate` function checks that the type of `value` we received from
|
|
// the API is what expect and narrows the type accordingly, allowing us to call `parse`.
|
|
if (propertyOptions.validate(value)) {
|
|
properties[name] = propertyOptions.parse(name, value, logger);
|
|
} else {
|
|
throw new Error(
|
|
`Unexpected value for repository property '${name}' (${typeof value}), got: ${JSON.stringify(value)}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
/** Parse a boolean repository property. */
|
|
function parseBooleanRepositoryProperty(
|
|
name: string,
|
|
value: string,
|
|
logger: Logger,
|
|
): boolean {
|
|
if (value !== "true" && value !== "false") {
|
|
logger.warning(
|
|
`Repository property '${name}' has unexpected value '${value}'. Expected 'true' or 'false'. Defaulting to false.`,
|
|
);
|
|
}
|
|
return value === "true";
|
|
}
|
|
|
|
/** Parse a string repository property. */
|
|
function parseStringRepositoryProperty(_name: string, value: string): string {
|
|
return value;
|
|
}
|
|
|
|
/** Set of known repository property names, for fast lookups. */
|
|
const KNOWN_REPOSITORY_PROPERTY_NAMES = new Set<string>(
|
|
Object.values(RepositoryPropertyName),
|
|
);
|
|
|
|
/** Returns whether the given value is a known repository property name. */
|
|
function isKnownPropertyName(name: string): name is RepositoryPropertyName {
|
|
return KNOWN_REPOSITORY_PROPERTY_NAMES.has(name);
|
|
}
|