mirror of
https://github.com/github/codeql-action.git
synced 2026-10-29 17:17:15 +00:00
Merge pull request #4037 from github/mbg/repo-props/tools
Add repository property for `tools` input
This commit is contained in:
139
lib/entry-points.js
generated
139
lib/entry-points.js
generated
@@ -146430,6 +146430,7 @@ async function createStatusReportBase(actionName, status, actionStartedAt, confi
|
||||
analysis_key,
|
||||
build_mode: config?.buildMode,
|
||||
commit_oid: commitOid,
|
||||
computed_inputs: {},
|
||||
first_party_analysis: isFirstPartyAnalysis(actionName),
|
||||
job_name: jobName,
|
||||
job_run_uuid: jobRunUUID,
|
||||
@@ -147008,6 +147009,11 @@ var featureConfig = {
|
||||
envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE",
|
||||
minimumVersion: void 0
|
||||
},
|
||||
["tools_repository_property" /* ToolsRepositoryProperty */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_TOOLS_REPOSITORY_PROPERTY",
|
||||
minimumVersion: void 0
|
||||
},
|
||||
["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API",
|
||||
@@ -147973,12 +147979,14 @@ function getUnknownLanguagesError(languages) {
|
||||
}
|
||||
|
||||
// src/feature-flags/properties.ts
|
||||
var github2 = __toESM(require_github());
|
||||
var GITHUB_CODEQL_PROPERTY_PREFIX = "github-codeql-";
|
||||
var RepositoryPropertyName = /* @__PURE__ */ ((RepositoryPropertyName2) => {
|
||||
RepositoryPropertyName2["CONFIG_FILE"] = "github-codeql-config-file";
|
||||
RepositoryPropertyName2["DISABLE_OVERLAY"] = "github-codeql-disable-overlay";
|
||||
RepositoryPropertyName2["EXTRA_QUERIES"] = "github-codeql-extra-queries";
|
||||
RepositoryPropertyName2["FILE_COVERAGE_ON_PRS"] = "github-codeql-file-coverage-on-prs";
|
||||
RepositoryPropertyName2["TOOLS"] = "github-codeql-tools";
|
||||
return RepositoryPropertyName2;
|
||||
})(RepositoryPropertyName || {});
|
||||
function isString2(value) {
|
||||
@@ -147997,7 +148005,8 @@ var repositoryPropertyParsers = {
|
||||
["github-codeql-config-file" /* CONFIG_FILE */]: stringProperty,
|
||||
["github-codeql-disable-overlay" /* DISABLE_OVERLAY */]: booleanProperty,
|
||||
["github-codeql-extra-queries" /* EXTRA_QUERIES */]: stringProperty,
|
||||
["github-codeql-file-coverage-on-prs" /* FILE_COVERAGE_ON_PRS */]: booleanProperty
|
||||
["github-codeql-file-coverage-on-prs" /* FILE_COVERAGE_ON_PRS */]: booleanProperty,
|
||||
["github-codeql-tools" /* TOOLS */]: stringProperty
|
||||
};
|
||||
async function loadPropertiesFromApi(logger, repositoryNwo) {
|
||||
try {
|
||||
@@ -148077,6 +148086,26 @@ var KNOWN_REPOSITORY_PROPERTY_NAMES = new Set(
|
||||
function isKnownPropertyName(name) {
|
||||
return KNOWN_REPOSITORY_PROPERTY_NAMES.has(name);
|
||||
}
|
||||
async function loadRepositoryProperties(repositoryNwo, logger) {
|
||||
const repositoryOwnerType = github2.context.payload.repository?.owner.type;
|
||||
logger.debug(
|
||||
`Repository owner type is '${repositoryOwnerType ?? "unknown"}'.`
|
||||
);
|
||||
if (repositoryOwnerType === "User") {
|
||||
logger.debug(
|
||||
"Skipping loading repository properties because the repository is owned by a user and therefore cannot have repository properties."
|
||||
);
|
||||
return new Success({});
|
||||
}
|
||||
try {
|
||||
return new Success(await loadPropertiesFromApi(logger, repositoryNwo));
|
||||
} catch (error3) {
|
||||
logger.warning(
|
||||
`Failed to load repository properties: ${getErrorMessage(error3)}`
|
||||
);
|
||||
return new Failure(error3);
|
||||
}
|
||||
}
|
||||
|
||||
// src/config/db-config.ts
|
||||
var ORG_SCHEMA = {
|
||||
@@ -154035,7 +154064,7 @@ var fs19 = __toESM(require("fs"));
|
||||
var path17 = __toESM(require("path"));
|
||||
var core14 = __toESM(require_core());
|
||||
var toolrunner4 = __toESM(require_toolrunner());
|
||||
var github2 = __toESM(require_github());
|
||||
var github3 = __toESM(require_github());
|
||||
var io6 = __toESM(require_io());
|
||||
async function initCodeQL(toolsInput, apiDetails, tempDir, variant, defaultCliVersion, rawLanguages, useOverlayAwareDefaultCliVersion, features, logger) {
|
||||
logger.startGroup("Setup CodeQL tools");
|
||||
@@ -154232,7 +154261,7 @@ function logFileCoverageOnPrsDeprecationWarning(logger) {
|
||||
if (process.env["CODEQL_ACTION_DID_LOG_FILE_COVERAGE_ON_PRS_DEPRECATION" /* DID_LOG_FILE_COVERAGE_ON_PRS_DEPRECATION */]) {
|
||||
return;
|
||||
}
|
||||
const repositoryOwnerType = github2.context.payload.repository?.owner.type;
|
||||
const repositoryOwnerType = github3.context.payload.repository?.owner.type;
|
||||
let message = "Starting April 2026, the CodeQL Action will skip computing file coverage information on pull requests to improve analysis performance. File coverage information will still be computed on non-PR analyses.";
|
||||
const envVarOptOut = "set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.";
|
||||
const repoPropertyOptOut = 'create a custom repository property with the name `github-codeql-file-coverage-on-prs` and the type "True/false", then set this property to `true` in the repository\'s settings.';
|
||||
@@ -157359,7 +157388,7 @@ var import_async = __toESM(require_async(), 1);
|
||||
var import_path6 = require("path");
|
||||
|
||||
// node_modules/archiver/lib/error.js
|
||||
var import_util32 = __toESM(require("util"), 1);
|
||||
var import_util33 = __toESM(require("util"), 1);
|
||||
var ERROR_CODES = {
|
||||
ABORTED: "archive was aborted",
|
||||
DIRECTORYDIRPATHREQUIRED: "diretory dirpath argument must be a non-empty string value",
|
||||
@@ -157384,7 +157413,7 @@ function ArchiverError(code, data) {
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
}
|
||||
import_util32.default.inherits(ArchiverError, Error);
|
||||
import_util33.default.inherits(ArchiverError, Error);
|
||||
|
||||
// node_modules/archiver/lib/core.js
|
||||
var import_readable_stream2 = __toESM(require_ours(), 1);
|
||||
@@ -160315,10 +160344,45 @@ async function runWrapper3() {
|
||||
var fs28 = __toESM(require("fs"));
|
||||
var path24 = __toESM(require("path"));
|
||||
var core21 = __toESM(require_core());
|
||||
var github3 = __toESM(require_github());
|
||||
var io7 = __toESM(require_io());
|
||||
var semver10 = __toESM(require_semver2());
|
||||
|
||||
// src/config/inputs.ts
|
||||
async function getToolsInput(action, repositoryProperties) {
|
||||
const name = "tools" /* Tools */;
|
||||
const input = action.actions.getOptionalInput(name);
|
||||
const propertyValue = repositoryProperties["github-codeql-tools" /* TOOLS */];
|
||||
const allowRepositoryProperty = await action.features.getValue(
|
||||
"tools_repository_property" /* ToolsRepositoryProperty */
|
||||
);
|
||||
if (allowRepositoryProperty && propertyValue?.startsWith("!")) {
|
||||
action.logger.info(
|
||||
`Using ${name} input from repository property (enforced): ${propertyValue}`
|
||||
);
|
||||
return {
|
||||
name,
|
||||
// Drop the '!' from the value.
|
||||
value: propertyValue.substring(1),
|
||||
source: "repository-property" /* RepositoryProperty */
|
||||
};
|
||||
}
|
||||
if (input !== void 0) {
|
||||
action.logger.info(`Using ${name} input from workflow: ${input}`);
|
||||
return { name, value: input, source: "workflow" /* Workflow */ };
|
||||
}
|
||||
if (allowRepositoryProperty && propertyValue !== void 0) {
|
||||
action.logger.info(
|
||||
`Using ${name} input from repository property: ${propertyValue}`
|
||||
);
|
||||
return {
|
||||
name,
|
||||
value: propertyValue,
|
||||
source: "repository-property" /* RepositoryProperty */
|
||||
};
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// src/workflow.ts
|
||||
var fs27 = __toESM(require("fs"));
|
||||
var path23 = __toESM(require("path"));
|
||||
@@ -160609,7 +160673,7 @@ async function sendStartingStatusReport(startedAt, config, logger) {
|
||||
await sendStatusReport(statusReportBase);
|
||||
}
|
||||
}
|
||||
async function sendCompletedStatusReport2(startedAt, config, configFile, toolsDownloadStatusReport, toolsFeatureFlagsValid, toolsSource, toolsVersion, overlayBaseDatabaseStats, dependencyCachingResults, logger, error3) {
|
||||
async function sendCompletedStatusReport2(startedAt, config, configFile, toolsInput, toolsDownloadStatusReport, toolsFeatureFlagsValid, toolsSource, toolsVersion, overlayBaseDatabaseStats, dependencyCachingResults, logger, error3) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
"init" /* Init */,
|
||||
getActionsStatus(error3),
|
||||
@@ -160626,11 +160690,14 @@ async function sendCompletedStatusReport2(startedAt, config, configFile, toolsDo
|
||||
const workflowLanguages = getOptionalInput("languages");
|
||||
const initStatusReport = {
|
||||
...statusReportBase,
|
||||
tools_input: getOptionalInput("tools") || "",
|
||||
tools_input: toolsInput?.value || "",
|
||||
tools_resolved_version: toolsVersion,
|
||||
tools_source: toolsSource || "UNKNOWN" /* Unknown */,
|
||||
workflow_languages: workflowLanguages || ""
|
||||
};
|
||||
if (toolsInput !== void 0) {
|
||||
initStatusReport.computed_inputs.tools = toolsInput;
|
||||
}
|
||||
const initToolsDownloadFields = {};
|
||||
if (toolsDownloadStatusReport?.downloadDurationMs !== void 0) {
|
||||
initToolsDownloadFields.tools_download_duration_ms = toolsDownloadStatusReport.downloadDurationMs;
|
||||
@@ -160666,6 +160733,7 @@ async function run3(actionState) {
|
||||
let codeql;
|
||||
let features;
|
||||
let sourceRoot;
|
||||
let toolsInput;
|
||||
let toolsDownloadStatusReport;
|
||||
let toolsFeatureFlagsValid;
|
||||
let toolsSource;
|
||||
@@ -160721,6 +160789,10 @@ async function run3(actionState) {
|
||||
`The 'init' action should not be run in the same workflow as 'setup-codeql'.`
|
||||
);
|
||||
}
|
||||
toolsInput = await getToolsInput(
|
||||
actionStateWithFeatures,
|
||||
repositoryProperties
|
||||
);
|
||||
const codeQLDefaultVersionInfo = await features.getEnabledDefaultCliVersions(gitHubVersion.type);
|
||||
toolsFeatureFlagsValid = codeQLDefaultVersionInfo.toolsFeatureFlagsValid;
|
||||
const rawLanguages = getRawLanguagesNoAutodetect(
|
||||
@@ -160728,7 +160800,7 @@ async function run3(actionState) {
|
||||
);
|
||||
const useOverlayAwareDefaultCliVersion = analysisKinds?.length === 1 && analysisKinds[0] === "code-scanning" /* CodeScanning */;
|
||||
const initCodeQLResult = await initCodeQL(
|
||||
getOptionalInput("tools"),
|
||||
toolsInput?.value,
|
||||
apiDetails,
|
||||
getTemporaryDirectory(),
|
||||
gitHubVersion.type,
|
||||
@@ -161046,6 +161118,7 @@ exec ${goBinaryPath} "$@"`
|
||||
config,
|
||||
void 0,
|
||||
// We only report config info on success.
|
||||
toolsInput,
|
||||
toolsDownloadStatusReport,
|
||||
toolsFeatureFlagsValid,
|
||||
toolsSource,
|
||||
@@ -161063,6 +161136,7 @@ exec ${goBinaryPath} "$@"`
|
||||
startedAt,
|
||||
config,
|
||||
configFile,
|
||||
toolsInput,
|
||||
toolsDownloadStatusReport,
|
||||
toolsFeatureFlagsValid,
|
||||
toolsSource,
|
||||
@@ -161072,26 +161146,6 @@ exec ${goBinaryPath} "$@"`
|
||||
logger
|
||||
);
|
||||
}
|
||||
async function loadRepositoryProperties(repositoryNwo, logger) {
|
||||
const repositoryOwnerType = github3.context.payload.repository?.owner.type;
|
||||
logger.debug(
|
||||
`Repository owner type is '${repositoryOwnerType ?? "unknown"}'.`
|
||||
);
|
||||
if (repositoryOwnerType === "User") {
|
||||
logger.debug(
|
||||
"Skipping loading repository properties because the repository is owned by a user and therefore cannot have repository properties."
|
||||
);
|
||||
return new Success({});
|
||||
}
|
||||
try {
|
||||
return new Success(await loadPropertiesFromApi(logger, repositoryNwo));
|
||||
} catch (error3) {
|
||||
logger.warning(
|
||||
`Failed to load repository properties: ${getErrorMessage(error3)}`
|
||||
);
|
||||
return new Failure(error3);
|
||||
}
|
||||
}
|
||||
var init = {
|
||||
name: "init" /* Init */,
|
||||
run: run3
|
||||
@@ -161642,7 +161696,7 @@ async function runWrapper6() {
|
||||
|
||||
// src/setup-codeql-action.ts
|
||||
var core24 = __toESM(require_core());
|
||||
async function sendCompletedStatusReport3(startedAt, toolsDownloadStatusReport, toolsFeatureFlagsValid, toolsSource, toolsVersion, logger, error3) {
|
||||
async function sendCompletedStatusReport3(startedAt, toolsInput, toolsDownloadStatusReport, toolsFeatureFlagsValid, toolsSource, toolsVersion, logger, error3) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
"setup-codeql" /* SetupCodeQL */,
|
||||
getActionsStatus(error3),
|
||||
@@ -161658,11 +161712,14 @@ async function sendCompletedStatusReport3(startedAt, toolsDownloadStatusReport,
|
||||
}
|
||||
const initStatusReport = {
|
||||
...statusReportBase,
|
||||
tools_input: getOptionalInput("tools") || "",
|
||||
tools_input: toolsInput?.value || "",
|
||||
tools_resolved_version: toolsVersion,
|
||||
tools_source: toolsSource || "UNKNOWN" /* Unknown */,
|
||||
workflow_languages: ""
|
||||
};
|
||||
if (toolsInput !== void 0) {
|
||||
initStatusReport.computed_inputs.tools = toolsInput;
|
||||
}
|
||||
const initToolsDownloadFields = {};
|
||||
if (toolsDownloadStatusReport?.downloadDurationMs !== void 0) {
|
||||
initToolsDownloadFields.tools_download_duration_ms = toolsDownloadStatusReport.downloadDurationMs;
|
||||
@@ -161672,11 +161729,10 @@ async function sendCompletedStatusReport3(startedAt, toolsDownloadStatusReport,
|
||||
}
|
||||
await sendStatusReport({ ...initStatusReport, ...initToolsDownloadFields });
|
||||
}
|
||||
async function run6({
|
||||
startedAt,
|
||||
logger
|
||||
}) {
|
||||
async function run6(actionState) {
|
||||
const { logger, startedAt } = actionState;
|
||||
let codeql;
|
||||
let toolsInput;
|
||||
let toolsDownloadStatusReport;
|
||||
let toolsFeatureFlagsValid;
|
||||
let toolsSource;
|
||||
@@ -161699,6 +161755,12 @@ async function run6({
|
||||
getTemporaryDirectory(),
|
||||
logger
|
||||
);
|
||||
const repositoryPropertiesResult = await loadRepositoryProperties(
|
||||
repositoryNwo,
|
||||
logger
|
||||
);
|
||||
const repositoryProperties = repositoryPropertiesResult.orElse({});
|
||||
const actionStateWithFeatures = { ...actionState, features };
|
||||
const jobRunUuid = v4_default();
|
||||
logger.info(`Job run UUID is ${jobRunUuid}.`);
|
||||
core24.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid);
|
||||
@@ -161713,6 +161775,10 @@ async function run6({
|
||||
if (statusReportBase !== void 0) {
|
||||
await sendStatusReport(statusReportBase);
|
||||
}
|
||||
toolsInput = await getToolsInput(
|
||||
actionStateWithFeatures,
|
||||
repositoryProperties
|
||||
);
|
||||
const codeQLDefaultVersionInfo = await features.getEnabledDefaultCliVersions(gitHubVersion.type);
|
||||
toolsFeatureFlagsValid = codeQLDefaultVersionInfo.toolsFeatureFlagsValid;
|
||||
const rawLanguages = getRawLanguagesNoAutodetect(
|
||||
@@ -161720,7 +161786,7 @@ async function run6({
|
||||
);
|
||||
const analysisKinds = await getAnalysisKinds(logger, features);
|
||||
const initCodeQLResult = await initCodeQL(
|
||||
getOptionalInput("tools"),
|
||||
toolsInput?.value,
|
||||
apiDetails,
|
||||
getTemporaryDirectory(),
|
||||
gitHubVersion.type,
|
||||
@@ -161757,6 +161823,7 @@ async function run6({
|
||||
}
|
||||
await sendCompletedStatusReport3(
|
||||
startedAt,
|
||||
toolsInput,
|
||||
toolsDownloadStatusReport,
|
||||
toolsFeatureFlagsValid,
|
||||
toolsSource,
|
||||
|
||||
99
src/config/inputs.test.ts
Normal file
99
src/config/inputs.test.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import test from "ava";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { getActionsEnv } from "../actions-util";
|
||||
import { Feature } from "../feature-flags";
|
||||
import { RepositoryPropertyName } from "../feature-flags/properties";
|
||||
import { callee } from "../testing-utils";
|
||||
|
||||
import { ComputedInput, getToolsInput, InputName, InputSource } from "./inputs";
|
||||
|
||||
test("getToolsInput - undefined if there's no input", async (t) => {
|
||||
await callee(getToolsInput).withArgs({}).passes(t.is, undefined);
|
||||
});
|
||||
|
||||
const expectedWorkflowResult: ComputedInput = {
|
||||
name: InputName.Tools,
|
||||
source: InputSource.Workflow,
|
||||
value: "workflow-input-value",
|
||||
};
|
||||
|
||||
const expectedRepositoryPropertyResult: ComputedInput = {
|
||||
name: InputName.Tools,
|
||||
source: InputSource.RepositoryProperty,
|
||||
value: "repo-property-input-value",
|
||||
};
|
||||
|
||||
function stubGetToolsInput() {
|
||||
const actions = getActionsEnv();
|
||||
sinon
|
||||
.stub(actions, "getOptionalInput")
|
||||
.withArgs(InputName.Tools)
|
||||
.returns(expectedWorkflowResult.value);
|
||||
return actions;
|
||||
}
|
||||
|
||||
const workflowLogMessage = `Using ${InputName.Tools} input from workflow:`;
|
||||
|
||||
test("getToolsInput - returns workflow input if available", async (t) => {
|
||||
const actions = stubGetToolsInput();
|
||||
|
||||
await callee(getToolsInput)
|
||||
.withActions(actions)
|
||||
.withArgs({})
|
||||
.logs(t, workflowLogMessage)
|
||||
.passes(t.deepEqual, expectedWorkflowResult);
|
||||
});
|
||||
|
||||
test("getToolsInput - returns repository property value if enforced", async (t) => {
|
||||
const actions = stubGetToolsInput();
|
||||
|
||||
const target = callee(getToolsInput)
|
||||
.withActions(actions)
|
||||
.withArgs({
|
||||
[RepositoryPropertyName.TOOLS]: `!${expectedRepositoryPropertyResult.value}`,
|
||||
});
|
||||
|
||||
// We expect the repository value if provided and the FF is enabled.
|
||||
const enforcedLogMessage = `Using ${InputName.Tools} input from repository property (enforced):`;
|
||||
await target
|
||||
.withFeatures([Feature.ToolsRepositoryProperty])
|
||||
.logs(t, enforcedLogMessage)
|
||||
.passes(t.deepEqual, expectedRepositoryPropertyResult);
|
||||
await target
|
||||
.notLogs(t, enforcedLogMessage)
|
||||
.logs(t, workflowLogMessage)
|
||||
.passes(t.deepEqual, expectedWorkflowResult);
|
||||
});
|
||||
|
||||
test("getToolsInput - prefers workflow input", async (t) => {
|
||||
const actions = stubGetToolsInput();
|
||||
|
||||
const target = callee(getToolsInput)
|
||||
.withActions(actions)
|
||||
.withArgs({
|
||||
[RepositoryPropertyName.TOOLS]: expectedRepositoryPropertyResult.value,
|
||||
});
|
||||
|
||||
// We expect the workflow input regardless of the FF state.
|
||||
await target
|
||||
.withFeatures([Feature.ToolsRepositoryProperty])
|
||||
.logs(t, workflowLogMessage)
|
||||
.passes(t.deepEqual, expectedWorkflowResult);
|
||||
await target
|
||||
.logs(t, workflowLogMessage)
|
||||
.passes(t.deepEqual, expectedWorkflowResult);
|
||||
});
|
||||
|
||||
test("getToolsInput - returns repository property", async (t) => {
|
||||
const target = callee(getToolsInput).withArgs({
|
||||
[RepositoryPropertyName.TOOLS]: expectedRepositoryPropertyResult.value,
|
||||
});
|
||||
|
||||
// We expect the repository property if the FF is enabled or undefined otherwise.
|
||||
await target
|
||||
.withFeatures([Feature.ToolsRepositoryProperty])
|
||||
.logs(t, `Using ${InputName.Tools} input from repository property:`)
|
||||
.passes(t.deepEqual, expectedRepositoryPropertyResult);
|
||||
await target.passes(t.is, undefined);
|
||||
});
|
||||
84
src/config/inputs.ts
Normal file
84
src/config/inputs.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { ActionState } from "../action-common";
|
||||
import { Feature } from "../feature-flags";
|
||||
import {
|
||||
RepositoryProperties,
|
||||
RepositoryPropertyName,
|
||||
} from "../feature-flags/properties";
|
||||
|
||||
/** Enumerates input names. */
|
||||
export enum InputName {
|
||||
Tools = "tools",
|
||||
}
|
||||
|
||||
/** Enumerates input sources. */
|
||||
export enum InputSource {
|
||||
Workflow = "workflow",
|
||||
RepositoryProperty = "repository-property",
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an effective input to the CodeQL Action. That is,
|
||||
* the input value that was computed or selected from multiple sources.
|
||||
*/
|
||||
export type ComputedInput = {
|
||||
/** The name of the property. */
|
||||
name: InputName;
|
||||
/** The value of the property. */
|
||||
value: string;
|
||||
/** The source of the property. */
|
||||
source: InputSource;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the computed `tools` input. This comes from either the workflow or
|
||||
* the repository property.
|
||||
*
|
||||
* @param action The Action state.
|
||||
* @param repositoryProperties The values of known repository properties.
|
||||
* @returns The computed input or `undefined` if there is no input.
|
||||
*/
|
||||
export async function getToolsInput(
|
||||
action: ActionState<["Logger", "Actions", "FeatureFlags"]>,
|
||||
repositoryProperties: Partial<RepositoryProperties>,
|
||||
): Promise<ComputedInput | undefined> {
|
||||
const name = InputName.Tools;
|
||||
const input = action.actions.getOptionalInput(name);
|
||||
const propertyValue = repositoryProperties[RepositoryPropertyName.TOOLS];
|
||||
const allowRepositoryProperty = await action.features.getValue(
|
||||
Feature.ToolsRepositoryProperty,
|
||||
);
|
||||
|
||||
// The repository property takes precedence if it starts with an '!'.
|
||||
if (allowRepositoryProperty && propertyValue?.startsWith("!")) {
|
||||
action.logger.info(
|
||||
`Using ${name} input from repository property (enforced): ${propertyValue}`,
|
||||
);
|
||||
return {
|
||||
name,
|
||||
// Drop the '!' from the value.
|
||||
value: propertyValue.substring(1),
|
||||
source: InputSource.RepositoryProperty,
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, the input from the workflow takes precedence.
|
||||
if (input !== undefined) {
|
||||
action.logger.info(`Using ${name} input from workflow: ${input}`);
|
||||
return { name, value: input, source: InputSource.Workflow };
|
||||
}
|
||||
|
||||
// Use the repository property if there's no workflow input.
|
||||
if (allowRepositoryProperty && propertyValue !== undefined) {
|
||||
action.logger.info(
|
||||
`Using ${name} input from repository property: ${propertyValue}`,
|
||||
);
|
||||
return {
|
||||
name,
|
||||
value: propertyValue,
|
||||
source: InputSource.RepositoryProperty,
|
||||
};
|
||||
}
|
||||
|
||||
// There's no input.
|
||||
return undefined;
|
||||
}
|
||||
@@ -141,6 +141,8 @@ export enum Feature {
|
||||
/** Note that this currently only disables baseline file coverage information. */
|
||||
SkipFileCoverageOnPrs = "skip_file_coverage_on_prs",
|
||||
StartProxyUseFeaturesRelease = "start_proxy_use_features_release",
|
||||
/** Whether to allow the `tools` input to be specified via a repository property. */
|
||||
ToolsRepositoryProperty = "tools_repository_property",
|
||||
UploadOverlayDbToApi = "upload_overlay_db_to_api",
|
||||
ValidateDbConfig = "validate_db_config",
|
||||
}
|
||||
@@ -400,6 +402,11 @@ export const featureConfig = {
|
||||
envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.ToolsRepositoryProperty]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_TOOLS_REPOSITORY_PROPERTY",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.UploadOverlayDbToApi]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API",
|
||||
|
||||
@@ -72,13 +72,17 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial("loadPropertiesFromApi loads known properties", async (t) => {
|
||||
const knownProperties = [
|
||||
{ property_name: "github-codeql-config-file", value: "owner/repo" },
|
||||
{ property_name: "github-codeql-extra-queries", value: "+queries" },
|
||||
{ property_name: "github-codeql-tools", value: "nightly" },
|
||||
];
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [
|
||||
{ property_name: "github-codeql-config-file", value: "owner/repo" },
|
||||
{ property_name: "github-codeql-extra-queries", value: "+queries" },
|
||||
...knownProperties,
|
||||
{ property_name: "unknown-property", value: "something" },
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
});
|
||||
@@ -88,10 +92,12 @@ test.serial("loadPropertiesFromApi loads known properties", async (t) => {
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
);
|
||||
t.deepEqual(response, {
|
||||
"github-codeql-config-file": "owner/repo",
|
||||
"github-codeql-extra-queries": "+queries",
|
||||
});
|
||||
t.deepEqual(
|
||||
response,
|
||||
Object.fromEntries(
|
||||
knownProperties.map((prop) => [prop.property_name, prop.value]),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test.serial("loadPropertiesFromApi parses true boolean property", async (t) => {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import * as github from "@actions/github";
|
||||
|
||||
import { isDynamicWorkflow } from "../actions-util";
|
||||
import { getRepositoryProperties } from "../api-client";
|
||||
import { Logger } from "../logging";
|
||||
import { RepositoryNwo } from "../repository";
|
||||
import { Failure, getErrorMessage, Result, Success } from "../util";
|
||||
|
||||
/** The common prefix that we expect all of our repository properties to have. */
|
||||
export const GITHUB_CODEQL_PROPERTY_PREFIX = "github-codeql-";
|
||||
@@ -14,6 +17,7 @@ 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",
|
||||
TOOLS = "github-codeql-tools",
|
||||
}
|
||||
|
||||
/** Parsed types of the known repository properties. */
|
||||
@@ -22,6 +26,7 @@ export type AllRepositoryProperties = {
|
||||
[RepositoryPropertyName.DISABLE_OVERLAY]: boolean;
|
||||
[RepositoryPropertyName.EXTRA_QUERIES]: string;
|
||||
[RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: boolean;
|
||||
[RepositoryPropertyName.TOOLS]: string;
|
||||
};
|
||||
|
||||
/** Parsed repository properties. */
|
||||
@@ -33,6 +38,7 @@ export type RepositoryPropertyApiType = {
|
||||
[RepositoryPropertyName.DISABLE_OVERLAY]: string;
|
||||
[RepositoryPropertyName.EXTRA_QUERIES]: string;
|
||||
[RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: string;
|
||||
[RepositoryPropertyName.TOOLS]: string;
|
||||
};
|
||||
|
||||
/** The type of functions which take the `value` from the API and try to convert it to the type we want. */
|
||||
@@ -81,6 +87,7 @@ const repositoryPropertyParsers: {
|
||||
[RepositoryPropertyName.DISABLE_OVERLAY]: booleanProperty,
|
||||
[RepositoryPropertyName.EXTRA_QUERIES]: stringProperty,
|
||||
[RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: booleanProperty,
|
||||
[RepositoryPropertyName.TOOLS]: stringProperty,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -230,3 +237,35 @@ const KNOWN_REPOSITORY_PROPERTY_NAMES = new Set<string>(
|
||||
function isKnownPropertyName(name: string): name is RepositoryPropertyName {
|
||||
return KNOWN_REPOSITORY_PROPERTY_NAMES.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads [repository properties](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization) if applicable.
|
||||
*/
|
||||
export async function loadRepositoryProperties(
|
||||
repositoryNwo: RepositoryNwo,
|
||||
logger: Logger,
|
||||
): Promise<Result<RepositoryProperties, unknown>> {
|
||||
// See if we can skip loading repository properties early. In particular,
|
||||
// repositories owned by users cannot have repository properties, so we can
|
||||
// skip the API call entirely in that case.
|
||||
const repositoryOwnerType = github.context.payload.repository?.owner.type;
|
||||
logger.debug(
|
||||
`Repository owner type is '${repositoryOwnerType ?? "unknown"}'.`,
|
||||
);
|
||||
if (repositoryOwnerType === "User") {
|
||||
logger.debug(
|
||||
"Skipping loading repository properties because the repository is owned by a user and " +
|
||||
"therefore cannot have repository properties.",
|
||||
);
|
||||
return new Success({});
|
||||
}
|
||||
|
||||
try {
|
||||
return new Success(await loadPropertiesFromApi(logger, repositoryNwo));
|
||||
} catch (error) {
|
||||
logger.warning(
|
||||
`Failed to load repository properties: ${getErrorMessage(error)}`,
|
||||
);
|
||||
return new Failure(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as github from "@actions/github";
|
||||
import * as io from "@actions/io";
|
||||
import * as semver from "semver";
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
@@ -26,6 +25,7 @@ import {
|
||||
} from "./caching-utils";
|
||||
import { CodeQL } from "./codeql";
|
||||
import { getConfigFileInput } from "./config/file";
|
||||
import { ComputedInput, getToolsInput } from "./config/inputs";
|
||||
import * as configUtils from "./config-utils";
|
||||
import {
|
||||
DependencyCacheRestoreStatusReport,
|
||||
@@ -41,10 +41,7 @@ import {
|
||||
} from "./diagnostics";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Feature, FeatureEnablement, initFeatures } from "./feature-flags";
|
||||
import {
|
||||
loadPropertiesFromApi,
|
||||
RepositoryProperties,
|
||||
} from "./feature-flags/properties";
|
||||
import { loadRepositoryProperties } from "./feature-flags/properties";
|
||||
import {
|
||||
checkInstallPython311,
|
||||
checkPacksForOverlayCompatibility,
|
||||
@@ -62,7 +59,7 @@ import {
|
||||
OverlayBaseDatabaseDownloadStats,
|
||||
} from "./overlay/caching";
|
||||
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
|
||||
import { getRepositoryNwo, RepositoryNwo } from "./repository";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
import { ToolsSource } from "./setup-codeql";
|
||||
import {
|
||||
ActionName,
|
||||
@@ -93,10 +90,7 @@ import {
|
||||
checkActionVersion,
|
||||
getErrorMessage,
|
||||
BuildMode,
|
||||
Result,
|
||||
getOptionalEnvVar,
|
||||
Success,
|
||||
Failure,
|
||||
} from "./util";
|
||||
import { checkWorkflow } from "./workflow";
|
||||
|
||||
@@ -136,6 +130,7 @@ async function sendCompletedStatusReport(
|
||||
startedAt: Date,
|
||||
config: configUtils.Config | undefined,
|
||||
configFile: string | undefined,
|
||||
toolsInput: ComputedInput | undefined,
|
||||
toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined,
|
||||
toolsFeatureFlagsValid: boolean | undefined,
|
||||
toolsSource: ToolsSource,
|
||||
@@ -164,12 +159,16 @@ async function sendCompletedStatusReport(
|
||||
|
||||
const initStatusReport: InitStatusReport = {
|
||||
...statusReportBase,
|
||||
tools_input: getOptionalInput("tools") || "",
|
||||
tools_input: toolsInput?.value || "",
|
||||
tools_resolved_version: toolsVersion,
|
||||
tools_source: toolsSource || ToolsSource.Unknown,
|
||||
workflow_languages: workflowLanguages || "",
|
||||
};
|
||||
|
||||
if (toolsInput !== undefined) {
|
||||
initStatusReport.computed_inputs.tools = toolsInput;
|
||||
}
|
||||
|
||||
const initToolsDownloadFields: InitToolsDownloadFields = {};
|
||||
|
||||
if (toolsDownloadStatusReport?.downloadDurationMs !== undefined) {
|
||||
@@ -217,6 +216,7 @@ async function run(
|
||||
let codeql: CodeQL;
|
||||
let features: FeatureEnablement;
|
||||
let sourceRoot: string;
|
||||
let toolsInput: ComputedInput | undefined;
|
||||
let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined;
|
||||
let toolsFeatureFlagsValid: boolean | undefined;
|
||||
let toolsSource: ToolsSource;
|
||||
@@ -300,6 +300,12 @@ async function run(
|
||||
);
|
||||
}
|
||||
|
||||
// Get the computed `tools` input.
|
||||
toolsInput = await getToolsInput(
|
||||
actionStateWithFeatures,
|
||||
repositoryProperties,
|
||||
);
|
||||
|
||||
const codeQLDefaultVersionInfo =
|
||||
await features.getEnabledDefaultCliVersions(gitHubVersion.type);
|
||||
toolsFeatureFlagsValid = codeQLDefaultVersionInfo.toolsFeatureFlagsValid;
|
||||
@@ -310,7 +316,7 @@ async function run(
|
||||
analysisKinds?.length === 1 &&
|
||||
analysisKinds[0] === AnalysisKind.CodeScanning;
|
||||
const initCodeQLResult = await initCodeQL(
|
||||
getOptionalInput("tools"),
|
||||
toolsInput?.value,
|
||||
apiDetails,
|
||||
getTemporaryDirectory(),
|
||||
gitHubVersion.type,
|
||||
@@ -759,6 +765,7 @@ async function run(
|
||||
startedAt,
|
||||
config,
|
||||
undefined, // We only report config info on success.
|
||||
toolsInput,
|
||||
toolsDownloadStatusReport,
|
||||
toolsFeatureFlagsValid,
|
||||
toolsSource,
|
||||
@@ -776,6 +783,7 @@ async function run(
|
||||
startedAt,
|
||||
config,
|
||||
configFile,
|
||||
toolsInput,
|
||||
toolsDownloadStatusReport,
|
||||
toolsFeatureFlagsValid,
|
||||
toolsSource,
|
||||
@@ -786,38 +794,6 @@ async function run(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads [repository properties](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization) if applicable.
|
||||
*/
|
||||
async function loadRepositoryProperties(
|
||||
repositoryNwo: RepositoryNwo,
|
||||
logger: Logger,
|
||||
): Promise<Result<RepositoryProperties, unknown>> {
|
||||
// See if we can skip loading repository properties early. In particular,
|
||||
// repositories owned by users cannot have repository properties, so we can
|
||||
// skip the API call entirely in that case.
|
||||
const repositoryOwnerType = github.context.payload.repository?.owner.type;
|
||||
logger.debug(
|
||||
`Repository owner type is '${repositoryOwnerType ?? "unknown"}'.`,
|
||||
);
|
||||
if (repositoryOwnerType === "User") {
|
||||
logger.debug(
|
||||
"Skipping loading repository properties because the repository is owned by a user and " +
|
||||
"therefore cannot have repository properties.",
|
||||
);
|
||||
return new Success({});
|
||||
}
|
||||
|
||||
try {
|
||||
return new Success(await loadPropertiesFromApi(logger, repositoryNwo));
|
||||
} catch (error) {
|
||||
logger.warning(
|
||||
`Failed to load repository properties: ${getErrorMessage(error)}`,
|
||||
);
|
||||
return new Failure(error);
|
||||
}
|
||||
}
|
||||
|
||||
/** Defines the `init` Action. */
|
||||
const init: Action = {
|
||||
name: ActionName.Init,
|
||||
|
||||
@@ -11,9 +11,11 @@ import {
|
||||
import { AnalysisKind, getAnalysisKinds } from "./analyses";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { CodeQL } from "./codeql";
|
||||
import { ComputedInput, getToolsInput } from "./config/inputs";
|
||||
import { getRawLanguagesNoAutodetect } from "./config-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import { initFeatures } from "./feature-flags";
|
||||
import { loadRepositoryProperties } from "./feature-flags/properties";
|
||||
import { initCodeQL } from "./init";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
@@ -43,6 +45,7 @@ import {
|
||||
*/
|
||||
async function sendCompletedStatusReport(
|
||||
startedAt: Date,
|
||||
toolsInput: ComputedInput | undefined,
|
||||
toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined,
|
||||
toolsFeatureFlagsValid: boolean | undefined,
|
||||
toolsSource: ToolsSource,
|
||||
@@ -67,12 +70,16 @@ async function sendCompletedStatusReport(
|
||||
|
||||
const initStatusReport: InitStatusReport = {
|
||||
...statusReportBase,
|
||||
tools_input: getOptionalInput("tools") || "",
|
||||
tools_input: toolsInput?.value || "",
|
||||
tools_resolved_version: toolsVersion,
|
||||
tools_source: toolsSource || ToolsSource.Unknown,
|
||||
workflow_languages: "",
|
||||
};
|
||||
|
||||
if (toolsInput !== undefined) {
|
||||
initStatusReport.computed_inputs.tools = toolsInput;
|
||||
}
|
||||
|
||||
const initToolsDownloadFields: InitToolsDownloadFields = {};
|
||||
|
||||
if (toolsDownloadStatusReport?.downloadDurationMs !== undefined) {
|
||||
@@ -87,14 +94,15 @@ async function sendCompletedStatusReport(
|
||||
}
|
||||
|
||||
/** The main behaviour of this action. */
|
||||
async function run({
|
||||
startedAt,
|
||||
logger,
|
||||
}: ActionState<["Base", "Logger"]>): Promise<void> {
|
||||
async function run(
|
||||
actionState: ActionState<["Base", "Logger", "Actions"]>,
|
||||
): Promise<void> {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
const { logger, startedAt } = actionState;
|
||||
|
||||
let codeql: CodeQL;
|
||||
let toolsInput: ComputedInput | undefined;
|
||||
let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined;
|
||||
let toolsFeatureFlagsValid: boolean | undefined;
|
||||
let toolsSource: ToolsSource;
|
||||
@@ -123,6 +131,15 @@ async function run({
|
||||
logger,
|
||||
);
|
||||
|
||||
// Fetch the values of known repository properties that affect us.
|
||||
const repositoryPropertiesResult = await loadRepositoryProperties(
|
||||
repositoryNwo,
|
||||
logger,
|
||||
);
|
||||
const repositoryProperties = repositoryPropertiesResult.orElse({});
|
||||
|
||||
const actionStateWithFeatures = { ...actionState, features };
|
||||
|
||||
const jobRunUuid = uuidV4();
|
||||
logger.info(`Job run UUID is ${jobRunUuid}.`);
|
||||
core.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid);
|
||||
@@ -138,6 +155,13 @@ async function run({
|
||||
if (statusReportBase !== undefined) {
|
||||
await sendStatusReport(statusReportBase);
|
||||
}
|
||||
|
||||
// Get the computed `tools` input.
|
||||
toolsInput = await getToolsInput(
|
||||
actionStateWithFeatures,
|
||||
repositoryProperties,
|
||||
);
|
||||
|
||||
const codeQLDefaultVersionInfo =
|
||||
await features.getEnabledDefaultCliVersions(gitHubVersion.type);
|
||||
toolsFeatureFlagsValid = codeQLDefaultVersionInfo.toolsFeatureFlagsValid;
|
||||
@@ -146,7 +170,7 @@ async function run({
|
||||
);
|
||||
const analysisKinds = await getAnalysisKinds(logger, features);
|
||||
const initCodeQLResult = await initCodeQL(
|
||||
getOptionalInput("tools"),
|
||||
toolsInput?.value,
|
||||
apiDetails,
|
||||
getTemporaryDirectory(),
|
||||
gitHubVersion.type,
|
||||
@@ -187,6 +211,7 @@ async function run({
|
||||
|
||||
await sendCompletedStatusReport(
|
||||
startedAt,
|
||||
toolsInput,
|
||||
toolsDownloadStatusReport,
|
||||
toolsFeatureFlagsValid,
|
||||
toolsSource,
|
||||
|
||||
@@ -71,6 +71,7 @@ test.serial("createStatusReportBase", async (t) => {
|
||||
t.is(statusReport.build_mode, BuildMode.None);
|
||||
t.is(statusReport.cause, "failure cause");
|
||||
t.is(statusReport.commit_oid, process.env["GITHUB_SHA"]!);
|
||||
t.deepEqual(statusReport.computed_inputs, {});
|
||||
t.is(statusReport.exception, "exception stack trace");
|
||||
t.is(statusReport.job_name, process.env["GITHUB_JOB"] || "");
|
||||
t.is(typeof statusReport.job_run_uuid, "string");
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "./actions-util";
|
||||
import { getAnalysisKey, getApiClient } from "./api-client";
|
||||
import type { Config } from "./config/action-config";
|
||||
import type { ComputedInput, InputName } from "./config/inputs";
|
||||
import { parseRegistriesWithoutCredentials } from "./config/pack-registries";
|
||||
import type { DependencyCacheRestoreStatusReport } from "./dependency-caching";
|
||||
import { DocUrl } from "./doc-url";
|
||||
@@ -122,6 +123,8 @@ export interface StatusReportBase {
|
||||
commit_oid: string;
|
||||
/** Time this action completed, or undefined if not yet completed. */
|
||||
completed_at?: string;
|
||||
/** A mapping of input names to their computed values. */
|
||||
computed_inputs: Partial<Record<InputName, ComputedInput>>;
|
||||
/** Stack trace of the failure (or undefined if status is not failure). */
|
||||
exception?: string;
|
||||
/** Whether this is a first-party (CodeQL) run of the action. */
|
||||
@@ -316,6 +319,7 @@ export async function createStatusReportBase(
|
||||
analysis_key,
|
||||
build_mode: config?.buildMode,
|
||||
commit_oid: commitOid,
|
||||
computed_inputs: {},
|
||||
first_party_analysis: isFirstPartyAnalysis(actionName),
|
||||
job_name: jobName,
|
||||
job_run_uuid: jobRunUUID,
|
||||
|
||||
Reference in New Issue
Block a user