diff --git a/CHANGELOG.md b/CHANGELOG.md index 039117fb25..d9218765f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th ## [UNRELEASED] -No user facing changes. +- Organizations can now create a custom repository property with the name `github-codeql-tools` to set the default CodeQL CLI tools value for their repositories. For more information, see [Managing custom properties for repositories in your organization](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization) and [Customizing your advanced setup for code scanning](https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning). ## 4.36.0 - 22 May 2026 diff --git a/lib/entry-points.js b/lib/entry-points.js index 78a5f058af..cdd24d2626 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -150394,11 +150394,13 @@ 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["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) { @@ -150416,7 +150418,8 @@ var booleanProperty = { var repositoryPropertyParsers = { ["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 { @@ -150469,6 +150472,26 @@ async function loadPropertiesFromApi(logger, repositoryNwo) { ); } } +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); + } +} function setProperty2(properties, name, value, logger) { const propertyOptions = repositoryPropertyParsers[name]; if (propertyOptions.validate(value)) { @@ -153360,7 +153383,7 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO ); } else { if (allowToolcacheValueFF) { - logger.warning( + logger.info( `Ignoring 'tools: ${toolsInput}' because the workflow was not triggered dynamically.` ); } else { @@ -156701,7 +156724,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"); @@ -156905,7 +156928,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.'; @@ -158689,10 +158712,25 @@ async function runWrapper3() { var fs27 = __toESM(require("fs")); var path23 = __toESM(require("path")); var core21 = __toESM(require_core()); -var github3 = __toESM(require_github()); var io7 = __toESM(require_io()); var semver10 = __toESM(require_semver2()); +// src/resolve-tools-input.ts +function resolveToolsInput(toolsWorkflowInput, repositoryProperties, logger) { + if (toolsWorkflowInput) { + logger.info(`Setting tools: ${toolsWorkflowInput} based on workflow input.`); + return toolsWorkflowInput; + } + const toolsPropertyValue = repositoryProperties["github-codeql-tools" /* TOOLS */]; + if (toolsPropertyValue) { + logger.info( + `Setting tools: ${toolsPropertyValue} based on the '${"github-codeql-tools" /* TOOLS */}' repository property.` + ); + return toolsPropertyValue; + } + return void 0; +} + // src/workflow.ts var fs26 = __toESM(require("fs")); var path22 = __toESM(require("path")); @@ -158983,7 +159021,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, toolsDownloadStatusReport, toolsFeatureFlagsValid, toolsSource, toolsVersion, effectiveToolsInput, overlayBaseDatabaseStats, dependencyCachingResults, logger, error3) { const statusReportBase = await createStatusReportBase( "init" /* Init */, getActionsStatus(error3), @@ -159001,6 +159039,7 @@ async function sendCompletedStatusReport2(startedAt, config, configFile, toolsDo const initStatusReport = { ...statusReportBase, tools_input: getOptionalInput("tools") || "", + effective_tools_input: effectiveToolsInput || "", tools_resolved_version: toolsVersion, tools_source: toolsSource || "UNKNOWN" /* Unknown */, workflow_languages: workflowLanguages || "" @@ -159044,6 +159083,7 @@ async function run3(startedAt) { let toolsSource; let toolsVersion; let zstdAvailability; + let effectiveToolsInput; try { initializeEnvironment(getActionVersion()); persistInputs(); @@ -159067,6 +159107,7 @@ async function run3(startedAt) { repositoryNwo, logger ); + const repositoryProperties = repositoryPropertiesResult.orElse({}); const jobRunUuid = v4_default(); logger.info(`Job run UUID is ${jobRunUuid}.`); core21.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); @@ -159092,12 +159133,17 @@ async function run3(startedAt) { } const codeQLDefaultVersionInfo = await features.getEnabledDefaultCliVersions(gitHubVersion.type); toolsFeatureFlagsValid = codeQLDefaultVersionInfo.toolsFeatureFlagsValid; + effectiveToolsInput = resolveToolsInput( + getOptionalInput("tools"), + repositoryProperties, + logger + ); const rawLanguages = getRawLanguagesNoAutodetect( getOptionalInput("languages") ); const useOverlayAwareDefaultCliVersion = analysisKinds?.length === 1 && analysisKinds[0] === "code-scanning" /* CodeScanning */; const initCodeQLResult = await initCodeQL( - getOptionalInput("tools"), + effectiveToolsInput, apiDetails, getTemporaryDirectory(), gitHubVersion.type, @@ -159133,7 +159179,6 @@ async function run3(startedAt) { } analysisKinds = await getAnalysisKinds(logger, features); const debugMode = getOptionalInput("debug") === "true" || core21.isDebug(); - const repositoryProperties = repositoryPropertiesResult.orElse({}); const fileCoverageResult = await getFileCoverageInformationEnabled( debugMode, codeql, @@ -159431,6 +159476,7 @@ exec ${goBinaryPath} "$@"` toolsFeatureFlagsValid, toolsSource, toolsVersion, + effectiveToolsInput, overlayBaseDatabaseStats, dependencyCachingStatus, logger, @@ -159448,31 +159494,12 @@ exec ${goBinaryPath} "$@"` toolsFeatureFlagsValid, toolsSource, toolsVersion, + effectiveToolsInput, overlayBaseDatabaseStats, dependencyCachingStatus, 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); - } -} async function recordZstdAvailability(config, zstdAvailability) { addNoLanguageDiagnostic( config, @@ -160041,7 +160068,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, toolsDownloadStatusReport, toolsFeatureFlagsValid, toolsSource, toolsVersion, effectiveToolsInput, logger, error3) { const statusReportBase = await createStatusReportBase( "setup-codeql" /* SetupCodeQL */, getActionsStatus(error3), @@ -160058,6 +160085,7 @@ async function sendCompletedStatusReport3(startedAt, toolsDownloadStatusReport, const initStatusReport = { ...statusReportBase, tools_input: getOptionalInput("tools") || "", + effective_tools_input: effectiveToolsInput || "", tools_resolved_version: toolsVersion, tools_source: toolsSource || "UNKNOWN" /* Unknown */, workflow_languages: "" @@ -160078,6 +160106,7 @@ async function run6(startedAt) { let toolsFeatureFlagsValid; let toolsSource; let toolsVersion; + let effectiveToolsInput; try { initializeEnvironment(getActionVersion()); const apiDetails = { @@ -160112,12 +160141,22 @@ async function run6(startedAt) { } const codeQLDefaultVersionInfo = await features.getEnabledDefaultCliVersions(gitHubVersion.type); toolsFeatureFlagsValid = codeQLDefaultVersionInfo.toolsFeatureFlagsValid; + const repositoryPropertiesResult = await loadRepositoryProperties( + repositoryNwo, + logger + ); + const repositoryProperties = repositoryPropertiesResult.orElse({}); + effectiveToolsInput = resolveToolsInput( + getOptionalInput("tools"), + repositoryProperties, + logger + ); const rawLanguages = getRawLanguagesNoAutodetect( getOptionalInput("languages") ); const analysisKinds = await getAnalysisKinds(logger, features); const initCodeQLResult = await initCodeQL( - getOptionalInput("tools"), + effectiveToolsInput, apiDetails, getTemporaryDirectory(), gitHubVersion.type, @@ -160158,6 +160197,7 @@ async function run6(startedAt) { toolsFeatureFlagsValid, toolsSource, toolsVersion, + effectiveToolsInput, logger ); } diff --git a/src/analyze-action.ts b/src/analyze-action.ts index cc2777bc5f..4b22f2dc55 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -30,7 +30,7 @@ import { } from "./dependency-caching"; import { EnvVar } from "./environment"; import { initFeatures } from "./feature-flags"; -import { BuiltInLanguage } from "./languages"; +import { BuiltInLanguage } from "./languages/index"; import { getActionsLogger, Logger } from "./logging"; import { cleanupAndUploadOverlayBaseDatabaseToCache } from "./overlay/caching"; import { getRepositoryNwo } from "./repository"; diff --git a/src/analyze.test.ts b/src/analyze.test.ts index 7523d239bf..e94ea80b57 100644 --- a/src/analyze.test.ts +++ b/src/analyze.test.ts @@ -14,7 +14,7 @@ import { } from "./analyze"; import { createStubCodeQL } from "./codeql"; import { Feature } from "./feature-flags"; -import { BuiltInLanguage } from "./languages"; +import { BuiltInLanguage } from "./languages/index"; import { getRunnerLogger } from "./logging"; import { setupTests, diff --git a/src/analyze.ts b/src/analyze.ts index 63830445d2..0d854cda9d 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -21,7 +21,7 @@ import { } from "./diff-informed-analysis-utils"; import { EnvVar } from "./environment"; import { FeatureEnablement, Feature } from "./feature-flags"; -import { BuiltInLanguage, Language } from "./languages"; +import { BuiltInLanguage, Language } from "./languages/index"; import { Logger, withGroupAsync } from "./logging"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; import type * as sarif from "./sarif"; diff --git a/src/autobuild-action.ts b/src/autobuild-action.ts index dc20211379..21244340eb 100644 --- a/src/autobuild-action.ts +++ b/src/autobuild-action.ts @@ -10,7 +10,7 @@ import { determineAutobuildLanguages, runAutobuild } from "./autobuild"; import { getCodeQL } from "./codeql"; import { Config, getConfig } from "./config-utils"; import { EnvVar } from "./environment"; -import { Language } from "./languages"; +import { Language } from "./languages/index"; import { Logger, getActionsLogger } from "./logging"; import { StatusReportBase, diff --git a/src/autobuild.ts b/src/autobuild.ts index fc4983f4ef..fdf4ef90a8 100644 --- a/src/autobuild.ts +++ b/src/autobuild.ts @@ -7,7 +7,7 @@ import * as configUtils from "./config-utils"; import { DocUrl } from "./doc-url"; import { EnvVar } from "./environment"; import { Feature, featureConfig, initFeatures } from "./feature-flags"; -import { BuiltInLanguage, Language } from "./languages"; +import { BuiltInLanguage, Language } from "./languages/index"; import { Logger } from "./logging"; import { getRepositoryNwo } from "./repository"; import { asyncFilter, BuildMode } from "./util"; diff --git a/src/codeql.test.ts b/src/codeql.test.ts index dea4cf04af..dd9faae92a 100644 --- a/src/codeql.test.ts +++ b/src/codeql.test.ts @@ -21,7 +21,7 @@ import { import type { Config } from "./config-utils"; import * as defaults from "./defaults.json"; import { DocUrl } from "./doc-url"; -import { BuiltInLanguage } from "./languages"; +import { BuiltInLanguage } from "./languages/index"; import { getRunnerLogger } from "./logging"; import { ToolsSource } from "./setup-codeql"; import { diff --git a/src/codeql.ts b/src/codeql.ts index 19f933c39a..80e1f1df58 100644 --- a/src/codeql.ts +++ b/src/codeql.ts @@ -22,7 +22,7 @@ import { FeatureEnablement, } from "./feature-flags"; import { isAnalyzingDefaultBranch } from "./git-utils"; -import { Language } from "./languages"; +import { Language } from "./languages/index"; import { Logger } from "./logging"; import { writeBaseDatabaseOidsFile, writeOverlayChangesFile } from "./overlay"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 10dee55399..79f7b0633e 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -18,7 +18,7 @@ import { Feature } from "./feature-flags"; import { RepositoryProperties } from "./feature-flags/properties"; import * as gitUtils from "./git-utils"; import { GitVersionInfo } from "./git-utils"; -import { BuiltInLanguage, Language } from "./languages"; +import { BuiltInLanguage, Language } from "./languages/index"; import { getRunnerLogger } from "./logging"; import { CODEQL_OVERLAY_MINIMUM_VERSION } from "./overlay"; import * as overlayDiagnostics from "./overlay/diagnostics"; diff --git a/src/config-utils.ts b/src/config-utils.ts index 87329fce2e..b6e5e8ef3c 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -48,7 +48,7 @@ import { hasSubmodules, isAnalyzingDefaultBranch, } from "./git-utils"; -import { BuiltInLanguage, Language } from "./languages"; +import { BuiltInLanguage, Language } from "./languages/index"; import { Logger } from "./logging"; import { CODEQL_OVERLAY_MINIMUM_VERSION } from "./overlay"; import { diff --git a/src/database-upload.test.ts b/src/database-upload.test.ts index 1cfbaecad6..45be8b5d26 100644 --- a/src/database-upload.test.ts +++ b/src/database-upload.test.ts @@ -12,7 +12,7 @@ import { createStubCodeQL } from "./codeql"; import { Config } from "./config-utils"; import { cleanupAndUploadDatabases } from "./database-upload"; import * as gitUtils from "./git-utils"; -import { BuiltInLanguage } from "./languages"; +import { BuiltInLanguage } from "./languages/index"; import { RepositoryNwo } from "./repository"; import { checkExpectedLogMessages, diff --git a/src/debug-artifacts.ts b/src/debug-artifacts.ts index 016fcdf7c4..2f97721547 100644 --- a/src/debug-artifacts.ts +++ b/src/debug-artifacts.ts @@ -13,7 +13,7 @@ import { type CodeQL } from "./codeql"; import { Config } from "./config-utils"; import { EnvVar } from "./environment"; import * as json from "./json"; -import { Language } from "./languages"; +import { Language } from "./languages/index"; import { Logger, withGroup } from "./logging"; import { isSafeArtifactUpload, diff --git a/src/dependency-caching.test.ts b/src/dependency-caching.test.ts index e611cd03eb..944f12c3c4 100644 --- a/src/dependency-caching.test.ts +++ b/src/dependency-caching.test.ts @@ -27,7 +27,7 @@ import { CacheStoreResult, } from "./dependency-caching"; import { Feature } from "./feature-flags"; -import { BuiltInLanguage } from "./languages"; +import { BuiltInLanguage } from "./languages/index"; import { setupTests, createFeatures, diff --git a/src/dependency-caching.ts b/src/dependency-caching.ts index f04d38f46c..fa8ff7b13e 100644 --- a/src/dependency-caching.ts +++ b/src/dependency-caching.ts @@ -11,7 +11,7 @@ import { CodeQL } from "./codeql"; import { Config } from "./config-utils"; import { EnvVar } from "./environment"; import { Feature, FeatureEnablement } from "./feature-flags"; -import { BuiltInLanguage, Language } from "./languages"; +import { BuiltInLanguage, Language } from "./languages/index"; import { Logger } from "./logging"; import { getErrorMessage, getRequiredEnvParam } from "./util"; diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 65e82ce1af..5bad57229b 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -2,7 +2,7 @@ import { existsSync, mkdirSync, writeFileSync } from "fs"; import path from "path"; import type { Config } from "./config-utils"; -import { Language } from "./languages"; +import { Language } from "./languages/index"; import { getActionsLogger } from "./logging"; import { getCodeQLDatabasePath } from "./util"; diff --git a/src/feature-flags/properties.test.ts b/src/feature-flags/properties.test.ts index 2676c2dcda..2bb9e10e4f 100644 --- a/src/feature-flags/properties.test.ts +++ b/src/feature-flags/properties.test.ts @@ -78,6 +78,7 @@ test.serial("loadPropertiesFromApi loads known properties", async (t) => { url: "", data: [ { property_name: "github-codeql-extra-queries", value: "+queries" }, + { property_name: "github-codeql-tools", value: "toolcache" }, { property_name: "unknown-property", value: "something" }, ] satisfies properties.GitHubPropertiesResponse, }); @@ -87,7 +88,10 @@ test.serial("loadPropertiesFromApi loads known properties", async (t) => { logger, mockRepositoryNwo, ); - t.deepEqual(response, { "github-codeql-extra-queries": "+queries" }); + t.deepEqual(response, { + "github-codeql-extra-queries": "+queries", + "github-codeql-tools": "toolcache", + }); }); test.serial("loadPropertiesFromApi parses true boolean property", async (t) => { diff --git a/src/feature-flags/properties.ts b/src/feature-flags/properties.ts index 12ba280bec..7eafadf918 100644 --- a/src/feature-flags/properties.ts +++ b/src/feature-flags/properties.ts @@ -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 { getErrorMessage, Result, Success, Failure } from "../util"; /** The common prefix that we expect all of our repository properties to have. */ export const GITHUB_CODEQL_PROPERTY_PREFIX = "github-codeql-"; @@ -13,6 +16,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. */ @@ -20,6 +24,7 @@ export type AllRepositoryProperties = { [RepositoryPropertyName.DISABLE_OVERLAY]: boolean; [RepositoryPropertyName.EXTRA_QUERIES]: string; [RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: boolean; + [RepositoryPropertyName.TOOLS]: string; }; /** Parsed repository properties. */ @@ -30,6 +35,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. */ @@ -77,6 +83,7 @@ const repositoryPropertyParsers: { [RepositoryPropertyName.DISABLE_OVERLAY]: booleanProperty, [RepositoryPropertyName.EXTRA_QUERIES]: stringProperty, [RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: booleanProperty, + [RepositoryPropertyName.TOOLS]: stringProperty, }; /** @@ -172,6 +179,38 @@ export async function loadPropertiesFromApi( } } +/** + * 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> { + // 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); + } +} + /** * 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. diff --git a/src/init-action.ts b/src/init-action.ts index 9d2619b1d1..1d0e6c3c13 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -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"; @@ -39,10 +38,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, @@ -53,14 +49,15 @@ import { initConfig, runDatabaseInitCluster, } from "./init"; -import { JavaEnvVars, BuiltInLanguage } from "./languages"; +import { JavaEnvVars, BuiltInLanguage } from "./languages/index"; import { getActionsLogger, Logger, withGroupAsync } from "./logging"; import { downloadOverlayBaseDatabaseFromCache, OverlayBaseDatabaseDownloadStats, } from "./overlay/caching"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; -import { getRepositoryNwo, RepositoryNwo } from "./repository"; +import { getRepositoryNwo } from "./repository"; +import { resolveToolsInput } from "./resolve-tools-input"; 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"; @@ -140,6 +134,7 @@ async function sendCompletedStatusReport( toolsFeatureFlagsValid: boolean | undefined, toolsSource: ToolsSource, toolsVersion: string, + effectiveToolsInput: string | undefined, overlayBaseDatabaseStats: OverlayBaseDatabaseDownloadStats | undefined, dependencyCachingResults: DependencyCacheRestoreStatusReport | undefined, logger: Logger, @@ -165,6 +160,7 @@ async function sendCompletedStatusReport( const initStatusReport: InitStatusReport = { ...statusReportBase, tools_input: getOptionalInput("tools") || "", + effective_tools_input: effectiveToolsInput || "", tools_resolved_version: toolsVersion, tools_source: toolsSource || ToolsSource.Unknown, workflow_languages: workflowLanguages || "", @@ -219,6 +215,7 @@ async function run(startedAt: Date) { let toolsSource: ToolsSource; let toolsVersion: string; let zstdAvailability: ZstdAvailability | undefined; + let effectiveToolsInput: string | undefined; try { initializeEnvironment(getActionVersion()); @@ -251,6 +248,7 @@ async function run(startedAt: Date) { repositoryNwo, logger, ); + const repositoryProperties = repositoryPropertiesResult.orElse({}); // Create a unique identifier for this run. const jobRunUuid = uuidV4(); @@ -296,6 +294,15 @@ async function run(startedAt: Date) { const codeQLDefaultVersionInfo = await features.getEnabledDefaultCliVersions(gitHubVersion.type); toolsFeatureFlagsValid = codeQLDefaultVersionInfo.toolsFeatureFlagsValid; + + // Determine the effective tools input. + // The explicit `tools` workflow input takes precedence. If none is provided, + // fall back to the 'github-codeql-tools' repository property (if set). + effectiveToolsInput = resolveToolsInput( + getOptionalInput("tools"), + repositoryProperties, + logger, + ); const rawLanguages = configUtils.getRawLanguagesNoAutodetect( getOptionalInput("languages"), ); @@ -303,7 +310,7 @@ async function run(startedAt: Date) { analysisKinds?.length === 1 && analysisKinds[0] === AnalysisKind.CodeScanning; const initCodeQLResult = await initCodeQL( - getOptionalInput("tools"), + effectiveToolsInput, apiDetails, getTemporaryDirectory(), gitHubVersion.type, @@ -350,7 +357,6 @@ async function run(startedAt: Date) { analysisKinds = await getAnalysisKinds(logger, features); const debugMode = getOptionalInput("debug") === "true" || core.isDebug(); - const repositoryProperties = repositoryPropertiesResult.orElse({}); const fileCoverageResult = await getFileCoverageInformationEnabled( debugMode, codeql, @@ -769,6 +775,7 @@ async function run(startedAt: Date) { toolsFeatureFlagsValid, toolsSource, toolsVersion, + effectiveToolsInput, overlayBaseDatabaseStats, dependencyCachingStatus, logger, @@ -786,44 +793,13 @@ async function run(startedAt: Date) { toolsFeatureFlagsValid, toolsSource, toolsVersion, + effectiveToolsInput, overlayBaseDatabaseStats, dependencyCachingStatus, logger, ); } -/** - * 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> { - // 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); - } -} - async function recordZstdAvailability( config: configUtils.Config, zstdAvailability: ZstdAvailability, diff --git a/src/init.test.ts b/src/init.test.ts index 88ad0c9b18..e92424fd7c 100644 --- a/src/init.test.ts +++ b/src/init.test.ts @@ -15,7 +15,7 @@ import { getFileCoverageInformationEnabled, logFileCoverageOnPrsDeprecationWarning, } from "./init"; -import { BuiltInLanguage } from "./languages"; +import { BuiltInLanguage } from "./languages/index"; import { createFeatures, LoggedMessage, diff --git a/src/init.ts b/src/init.ts index 2533d9a894..e88f825249 100644 --- a/src/init.ts +++ b/src/init.ts @@ -26,7 +26,7 @@ import { RepositoryProperties, RepositoryPropertyName, } from "./feature-flags/properties"; -import { BuiltInLanguage, Language } from "./languages"; +import { BuiltInLanguage, Language } from "./languages/index"; import { Logger, withGroupAsync } from "./logging"; import { ToolsSource } from "./setup-codeql"; import { ZstdAvailability } from "./tar"; diff --git a/src/resolve-tools-input.test.ts b/src/resolve-tools-input.test.ts new file mode 100644 index 0000000000..d6da2fa5d1 --- /dev/null +++ b/src/resolve-tools-input.test.ts @@ -0,0 +1,171 @@ +import test from "ava"; + +import { RepositoryPropertyName } from "./feature-flags/properties"; +import type { RepositoryProperties } from "./feature-flags/properties"; +import { resolveToolsInput } from "./resolve-tools-input"; +import { getRecordingLogger, LoggedMessage, setupTests } from "./testing-utils"; + +setupTests(test); + +test( + "resolveToolsInput returns undefined when no tools input or repository property is set", + (t) => { + const loggedMessages: LoggedMessage[] = []; + const logger = getRecordingLogger(loggedMessages); + + const result = resolveToolsInput(undefined, {}, logger); + + t.is(result, undefined); + t.is(loggedMessages.length, 0); + }, +); + +test( + "resolveToolsInput returns workflow input when only workflow input is provided", + (t) => { + const loggedMessages: LoggedMessage[] = []; + const logger = getRecordingLogger(loggedMessages); + + const result = resolveToolsInput("latest", {}, logger); + + t.is(result, "latest"); + t.is(loggedMessages.length, 1); + t.is( + loggedMessages[0].message, + "Setting tools: latest based on workflow input.", + ); + }, +); + +test( + "resolveToolsInput returns repository property when only repository property is provided", + (t) => { + const loggedMessages: LoggedMessage[] = []; + const logger = getRecordingLogger(loggedMessages); + + const repositoryProperties: RepositoryProperties = { + [RepositoryPropertyName.TOOLS]: "toolcache", + }; + const result = resolveToolsInput(undefined, repositoryProperties, logger); + + t.is(result, "toolcache"); + t.is(loggedMessages.length, 1); + t.is( + loggedMessages[0].message, + "Setting tools: toolcache based on the 'github-codeql-tools' repository property.", + ); + }, +); + +test( + "resolveToolsInput prioritizes workflow input over repository property", + (t) => { + const loggedMessages: LoggedMessage[] = []; + const logger = getRecordingLogger(loggedMessages); + + const repositoryProperties: RepositoryProperties = { + [RepositoryPropertyName.TOOLS]: "toolcache", + }; + const result = resolveToolsInput("nightly", repositoryProperties, logger); + + t.is(result, "nightly"); + t.is(loggedMessages.length, 1); + t.is( + loggedMessages[0].message, + "Setting tools: nightly based on workflow input.", + ); + }, +); + +test( + "resolveToolsInput treats empty string workflow input as not set", + (t) => { + const loggedMessages: LoggedMessage[] = []; + const logger = getRecordingLogger(loggedMessages); + + const repositoryProperties: RepositoryProperties = { + [RepositoryPropertyName.TOOLS]: "toolcache", + }; + const result = resolveToolsInput("", repositoryProperties, logger); + + t.is(result, "toolcache"); + t.is(loggedMessages.length, 1); + t.is( + loggedMessages[0].message, + "Setting tools: toolcache based on the 'github-codeql-tools' repository property.", + ); + }, +); + +test( + "resolveToolsInput returns workflow input with URL value", + (t) => { + const loggedMessages: LoggedMessage[] = []; + const logger = getRecordingLogger(loggedMessages); + + const url = "https://example.com/codeql-bundle.tar.gz"; + const result = resolveToolsInput(url, {}, logger); + + t.is(result, url); + t.is(loggedMessages.length, 1); + t.is( + loggedMessages[0].message, + `Setting tools: ${url} based on workflow input.`, + ); + }, +); + +test( + "resolveToolsInput returns repository property with 'latest' value", + (t) => { + const loggedMessages: LoggedMessage[] = []; + const logger = getRecordingLogger(loggedMessages); + + const repositoryProperties: RepositoryProperties = { + [RepositoryPropertyName.TOOLS]: "latest", + }; + const result = resolveToolsInput(undefined, repositoryProperties, logger); + + t.is(result, "latest"); + t.is( + loggedMessages[0].message, + "Setting tools: latest based on the 'github-codeql-tools' repository property.", + ); + }, +); + +test( + "resolveToolsInput returns repository property with specific version", + (t) => { + const loggedMessages: LoggedMessage[] = []; + const logger = getRecordingLogger(loggedMessages); + + const repositoryProperties: RepositoryProperties = { + [RepositoryPropertyName.TOOLS]: "2.16.1", + }; + const result = resolveToolsInput(undefined, repositoryProperties, logger); + + t.is(result, "2.16.1"); + t.is( + loggedMessages[0].message, + "Setting tools: 2.16.1 based on the 'github-codeql-tools' repository property.", + ); + }, +); + +test( + "resolveToolsInput returns undefined when repository property is undefined", + (t) => { + const loggedMessages: LoggedMessage[] = []; + const logger = getRecordingLogger(loggedMessages); + + const repositoryProperties: RepositoryProperties = { + [RepositoryPropertyName.TOOLS]: undefined, + }; + const result = resolveToolsInput(undefined, repositoryProperties, logger); + + t.is(result, undefined); + t.is(loggedMessages.length, 0); + }, +); + diff --git a/src/resolve-tools-input.ts b/src/resolve-tools-input.ts new file mode 100644 index 0000000000..f9aca4fdac --- /dev/null +++ b/src/resolve-tools-input.ts @@ -0,0 +1,36 @@ +import { + RepositoryProperties, + RepositoryPropertyName, +} from "./feature-flags/properties"; +import { Logger } from "./logging"; + +/** + * Resolves the effective tools input by combining the workflow input and repository properties. + * The explicit `tools` workflow input takes precedence. If none is provided, + * falls back to the repository property (if set). + * + * @param toolsWorkflowInput - The value of the `tools` workflow input, if provided. + * @param repositoryProperties - The parsed repository properties. + * @param logger - Logger for outputting resolution messages. + * @returns The effective tools input value. + */ +export function resolveToolsInput( + toolsWorkflowInput: string | undefined, + repositoryProperties: RepositoryProperties, + logger: Logger, +): string | undefined { + if (toolsWorkflowInput) { + logger.info(`Setting tools: ${toolsWorkflowInput} based on workflow input.`); + return toolsWorkflowInput; + } + + const toolsPropertyValue = repositoryProperties[RepositoryPropertyName.TOOLS]; + if (toolsPropertyValue) { + logger.info( + `Setting tools: ${toolsPropertyValue} based on the '${RepositoryPropertyName.TOOLS}' repository property.`, + ); + return toolsPropertyValue; + } + + return undefined; +} diff --git a/src/setup-codeql-action.ts b/src/setup-codeql-action.ts index d3e0e7dbcc..f0a5c821c5 100644 --- a/src/setup-codeql-action.ts +++ b/src/setup-codeql-action.ts @@ -13,9 +13,11 @@ import { CodeQL } from "./codeql"; 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 { getActionsLogger, Logger } from "./logging"; import { getRepositoryNwo } from "./repository"; +import { resolveToolsInput } from "./resolve-tools-input"; import { ToolsSource } from "./setup-codeql"; import { ActionName, @@ -48,6 +50,7 @@ async function sendCompletedStatusReport( toolsFeatureFlagsValid: boolean | undefined, toolsSource: ToolsSource, toolsVersion: string, + effectiveToolsInput: string | undefined, logger: Logger, error?: Error, ): Promise { @@ -69,6 +72,7 @@ async function sendCompletedStatusReport( const initStatusReport: InitStatusReport = { ...statusReportBase, tools_input: getOptionalInput("tools") || "", + effective_tools_input: effectiveToolsInput || "", tools_resolved_version: toolsVersion, tools_source: toolsSource || ToolsSource.Unknown, workflow_languages: "", @@ -99,6 +103,7 @@ async function run(startedAt: Date): Promise { let toolsFeatureFlagsValid: boolean | undefined; let toolsSource: ToolsSource; let toolsVersion: string; + let effectiveToolsInput: string | undefined; try { initializeEnvironment(getActionVersion()); @@ -141,12 +146,29 @@ async function run(startedAt: Date): Promise { const codeQLDefaultVersionInfo = await features.getEnabledDefaultCliVersions(gitHubVersion.type); toolsFeatureFlagsValid = codeQLDefaultVersionInfo.toolsFeatureFlagsValid; + + // Fetch the values of known repository properties that affect us. + const repositoryPropertiesResult = await loadRepositoryProperties( + repositoryNwo, + logger, + ); + const repositoryProperties = repositoryPropertiesResult.orElse({}); + + // Determine the effective tools input. + // The explicit `tools` workflow input takes precedence. If none is provided, + // fall back to the 'github-codeql-tools' repository property (if set). + effectiveToolsInput = resolveToolsInput( + getOptionalInput("tools"), + repositoryProperties, + logger, + ); const rawLanguages = getRawLanguagesNoAutodetect( getOptionalInput("languages"), ); const analysisKinds = await getAnalysisKinds(logger, features); + const initCodeQLResult = await initCodeQL( - getOptionalInput("tools"), + effectiveToolsInput, apiDetails, getTemporaryDirectory(), gitHubVersion.type, @@ -191,6 +213,7 @@ async function run(startedAt: Date): Promise { toolsFeatureFlagsValid, toolsSource, toolsVersion, + effectiveToolsInput, logger, ); } diff --git a/src/setup-codeql.ts b/src/setup-codeql.ts index 3db0b6ca4d..c654ba27a7 100644 --- a/src/setup-codeql.ts +++ b/src/setup-codeql.ts @@ -559,7 +559,7 @@ export async function getCodeQLSource( ); } else { if (allowToolcacheValueFF) { - logger.warning( + logger.info( `Ignoring 'tools: ${toolsInput}' because the workflow was not triggered dynamically.`, ); } else { diff --git a/src/start-proxy-action.ts b/src/start-proxy-action.ts index 609b576441..e3750af8ae 100644 --- a/src/start-proxy-action.ts +++ b/src/start-proxy-action.ts @@ -6,7 +6,7 @@ import * as core from "@actions/core"; import * as actionsUtil from "./actions-util"; import { getGitHubVersion } from "./api-client"; import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; -import { BuiltInLanguage, parseBuiltInLanguage } from "./languages"; +import { BuiltInLanguage, parseBuiltInLanguage } from "./languages/index"; import { getActionsLogger, Logger } from "./logging"; import { getRepositoryNwo } from "./repository"; import { diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index 9f12656f62..57822923f8 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -10,7 +10,7 @@ import * as defaults from "./defaults.json"; import { setUpFeatureFlagTests } from "./feature-flags/testing-util"; import { UnvalidatedObject, validateSchema } from "./json"; import { makeFromSchema } from "./json/testing-util"; -import { BuiltInLanguage } from "./languages"; +import { BuiltInLanguage } from "./languages/index"; import { getRunnerLogger, Logger } from "./logging"; import * as startProxyExports from "./start-proxy"; import * as statusReport from "./status-report"; diff --git a/src/start-proxy.ts b/src/start-proxy.ts index d6111510f6..3a123a7bb5 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -18,7 +18,7 @@ import { FeatureEnablement, } from "./feature-flags"; import * as json from "./json"; -import { BuiltInLanguage } from "./languages"; +import { BuiltInLanguage } from "./languages/index"; import { Logger } from "./logging"; import { Address, diff --git a/src/status-report.test.ts b/src/status-report.test.ts index 52132b7649..c08dfd8b1d 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -4,7 +4,7 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import { Config } from "./config-utils"; import { EnvVar } from "./environment"; -import { BuiltInLanguage } from "./languages"; +import { BuiltInLanguage } from "./languages/index"; import { getRunnerLogger } from "./logging"; import { ToolsSource } from "./setup-codeql"; import { @@ -316,6 +316,7 @@ const testCreateInitWithConfigStatusReport = makeMacro({ const initStatusReport: InitStatusReport = { ...statusReportBase, tools_input: "", + effective_tools_input: "", tools_resolved_version: "foo", tools_source: ToolsSource.Unknown, workflow_languages: "actions", diff --git a/src/status-report.ts b/src/status-report.ts index b3e3628b36..15c1a0ee46 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -482,6 +482,8 @@ export async function sendStatusReport( export interface InitStatusReport extends StatusReportBase { /** Value given by the user as the "tools" input. */ tools_input: string; + /** The effective tools input that was used, after applying defaults and repository properties. */ + effective_tools_input: string; /** Version of the bundle used. */ tools_resolved_version: string; /** Where the bundle originated from. */ diff --git a/src/tracer-config.test.ts b/src/tracer-config.test.ts index 58f844b8e7..b28749cfb2 100644 --- a/src/tracer-config.test.ts +++ b/src/tracer-config.test.ts @@ -6,7 +6,7 @@ import * as sinon from "sinon"; import { CodeQL, getCodeQLForTesting } from "./codeql"; import * as configUtils from "./config-utils"; -import { BuiltInLanguage } from "./languages"; +import { BuiltInLanguage } from "./languages/index"; import { createTestConfig, makeVersionInfo, setupTests } from "./testing-utils"; import { ToolsFeature } from "./tools-features"; import { getCombinedTracerConfig } from "./tracer-config"; diff --git a/src/trap-caching.test.ts b/src/trap-caching.test.ts index 9805475796..bc819721ef 100644 --- a/src/trap-caching.test.ts +++ b/src/trap-caching.test.ts @@ -15,7 +15,7 @@ import { import * as configUtils from "./config-utils"; import { Feature } from "./feature-flags"; import * as gitUtils from "./git-utils"; -import { BuiltInLanguage } from "./languages"; +import { BuiltInLanguage } from "./languages/index"; import { getRunnerLogger } from "./logging"; import { createFeatures, diff --git a/src/trap-caching.ts b/src/trap-caching.ts index 216122d47e..c105f201d1 100644 --- a/src/trap-caching.ts +++ b/src/trap-caching.ts @@ -10,7 +10,7 @@ import { type Config } from "./config-utils"; import { DocUrl } from "./doc-url"; import { Feature, FeatureEnablement } from "./feature-flags"; import * as gitUtils from "./git-utils"; -import { Language } from "./languages"; +import { Language } from "./languages/index"; import { Logger } from "./logging"; import { asHTTPError, diff --git a/src/util.ts b/src/util.ts index e2331461bd..2e053457da 100644 --- a/src/util.ts +++ b/src/util.ts @@ -15,7 +15,7 @@ import type { Pack } from "./config/db-config"; import type { Config } from "./config-utils"; import { EnvVar } from "./environment"; import * as json from "./json"; -import { Language } from "./languages"; +import { Language } from "./languages/index"; import { Logger } from "./logging"; /**