Merge pull request #4037 from github/mbg/repo-props/tools

Add repository property for `tools` input
This commit is contained in:
Michael B. Gale
2026-07-24 13:19:39 +00:00
committed by GitHub
10 changed files with 399 additions and 91 deletions

139
lib/entry-points.js generated
View File

@@ -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
View 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
View 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;
}

View File

@@ -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",

View File

@@ -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) => {

View File

@@ -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);
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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");

View File

@@ -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,