From 653a997aa374a2a90456f043a56f730217012819 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 11:01:31 +0100 Subject: [PATCH] Store partial configuration when `init` is aborted --- lib/entry-points.js | 54 ++++++++++++++--- src/config-utils.ts | 116 +++++++++++++++++++++++++++++++----- src/config/action-config.ts | 11 ++++ src/init-action.ts | 12 ++++ 4 files changed, 171 insertions(+), 22 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index f684edb29..a8acda9ee 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -175095,20 +175095,45 @@ function getLocalConfig(logger2, configFile, validateConfig) { function getPathToParsedConfigFile(tempDir) { return path11.join(tempDir, "config"); } -async function saveConfig(config, logger2) { +function getPathToFailedConfigFile(tempDir) { + return path11.join(tempDir, "failed-config"); +} +function saveConfigFile(logger2, configFilePath, config) { const configString = JSON.stringify(config); - const configFile = getPathToParsedConfigFile(config.tempDir); - fs11.mkdirSync(path11.dirname(configFile), { recursive: true }); - fs11.writeFileSync(configFile, configString, "utf8"); + fs11.mkdirSync(path11.dirname(configFilePath), { recursive: true }); + fs11.writeFileSync(configFilePath, configString, "utf8"); logger2.debug("Saved config:"); logger2.debug(configString); } -async function getConfig(tempDir, logger2) { - const configFile = getPathToParsedConfigFile(tempDir); - if (!fs11.existsSync(configFile)) { +async function saveConfig(config, logger2) { + const configFile = getPathToParsedConfigFile(config.tempDir); + saveConfigFile(logger2, configFile, config); +} +function saveAbortedInitConfig(action, config) { + if (action.name !== "init" /* Init */) { + return; + } + const failedConfig = { + version: config.version, + tempDir: config.tempDir, + codeQLCmd: config.codeQLCmd, + debugMode: config.debugMode, + gitHubVersion: config.gitHubVersion, + languages: config.languages, + analysisKinds: config.analysisKinds + }; + action.logger.debug(`Saving config for failed init step.`); + saveConfigFile( + action.logger, + getPathToFailedConfigFile(config.tempDir), + failedConfig + ); +} +function readConfigFile(logger2, configFilePath) { + if (!fs11.existsSync(configFilePath)) { return void 0; } - const configString = fs11.readFileSync(configFile, "utf8"); + const configString = fs11.readFileSync(configFilePath, "utf8"); logger2.debug("Loaded config:"); logger2.debug(configString); const config = JSON.parse(configString); @@ -175129,6 +175154,10 @@ async function getConfig(tempDir, logger2) { } return config; } +async function getConfig(tempDir, logger2) { + const configFile = getPathToParsedConfigFile(tempDir); + return readConfigFile(logger2, configFile); +} async function generateRegistries(registriesInput, tempDir, logger2) { const registries = parseRegistries(registriesInput); let registriesAuthTokens; @@ -186570,6 +186599,15 @@ async function run3(actionState) { } await checkInstallPython311(config.languages, codeql); } catch (unwrappedError) { + if (config !== void 0) { + try { + saveAbortedInitConfig(actionState, config); + } catch (configSaveError) { + logger2.warning( + `Unable to save configuration for '${"init-post" /* InitPost */}' step: ${getErrorMessage(configSaveError)}` + ); + } + } const error3 = wrapError(unwrappedError); core19.setFailed(error3.message); const statusReportBase = await createStatusReportBase( diff --git a/src/config-utils.ts b/src/config-utils.ts index ebfabad36..107f66666 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -21,7 +21,11 @@ import { import * as api from "./api-client"; import { getCachingKind } from "./caching-utils"; import { type CodeQL } from "./codeql"; -import { type ActionConfig, type Config } from "./config/action-config"; +import { + type AbortedInitConfig, + type ActionConfig, + type Config, +} from "./config/action-config"; import { calculateAugmentation, ExcludeQueryFilter, @@ -71,6 +75,7 @@ import { import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; import { shouldSkipOverlayAnalysis } from "./overlay/status"; import { RepositoryNwo } from "./repository"; +import { ActionName } from "./status-report"; import { ToolsFeature } from "./tools-features"; import { downloadTrapCaches } from "./trap-caching"; import { @@ -1379,31 +1384,84 @@ export function getPathToParsedConfigFile(tempDir: string): string { return path.join(tempDir, "config"); } +function getPathToFailedConfigFile(tempDir: string): string { + return path.join(tempDir, "failed-config"); +} + /** - * Store the given config to the path returned from getPathToParsedConfigFile. + * Writes {@link config} to {@link configFilePath}. + * + * This function is not intended to be called, other than by + * {@link saveConfig} and {@link saveFailedConfig} below. */ -export async function saveConfig(config: Config, logger: Logger) { +function saveConfigFile( + logger: Logger, + configFilePath: string, + config: Partial, +) { const configString = JSON.stringify(config); - const configFile = getPathToParsedConfigFile(config.tempDir); - fs.mkdirSync(path.dirname(configFile), { recursive: true }); - fs.writeFileSync(configFile, configString, "utf8"); + fs.mkdirSync(path.dirname(configFilePath), { recursive: true }); + fs.writeFileSync(configFilePath, configString, "utf8"); logger.debug("Saved config:"); logger.debug(configString); } /** - * Get the config that has been saved to the given temp dir. - * If the config could not be found then returns undefined. + * Store the given config to the path returned from getPathToParsedConfigFile. */ -export async function getConfig( - tempDir: string, +export async function saveConfig(config: Config, logger: Logger) { + const configFile = getPathToParsedConfigFile(config.tempDir); + saveConfigFile(logger, configFile, config); +} + +/** + * Store parts of {@link config} as a failed configuration file. + * + * The file will only contain parts of {@link config} that are relevant to + * the `init-post` step in the workflow. + */ +export function saveAbortedInitConfig( + action: ActionState<["Base", "Logger"]>, + config: Config, +) { + // Should only be called by init. + if (action.name !== ActionName.Init) { + return; + } + + // Create a copy of the input `Config` that only contains the keys we want. + const failedConfig: AbortedInitConfig = { + version: config.version, + tempDir: config.tempDir, + codeQLCmd: config.codeQLCmd, + debugMode: config.debugMode, + gitHubVersion: config.gitHubVersion, + languages: config.languages, + analysisKinds: config.analysisKinds, + }; + + action.logger.debug(`Saving config for failed init step.`); + saveConfigFile( + action.logger, + getPathToFailedConfigFile(config.tempDir), + failedConfig, + ); +} + +/** + * Attempts to read a {@link Config} from {@link configFilePath}. + * + * This function is not intended to be called, other than by + * {@link getConfig} and {@link getAbortedInitConfig} below. + */ +function readConfigFile( logger: Logger, -): Promise { - const configFile = getPathToParsedConfigFile(tempDir); - if (!fs.existsSync(configFile)) { + configFilePath: string, +): Config | undefined { + if (!fs.existsSync(configFilePath)) { return undefined; } - const configString = fs.readFileSync(configFile, "utf8"); + const configString = fs.readFileSync(configFilePath, "utf8"); logger.debug("Loaded config:"); logger.debug(configString); @@ -1428,6 +1486,36 @@ export async function getConfig( return config as Config; } +/** + * Get the config that has been saved to the given temp dir. + * If the config could not be found then returns undefined. + */ +export async function getConfig( + tempDir: string, + logger: Logger, +): Promise { + const configFile = getPathToParsedConfigFile(tempDir); + return readConfigFile(logger, configFile); +} + +/** + * Loads a {@link AbortedInitConfig} from a failed `init` step if it exists. + * + * The result will only contain parts of a {@link Config} that are relevant to + * the `init-post` step in the workflow. + */ +export function getAbortedInitConfig( + action: ActionState<["Base", "Logger"]>, + tempDir: string, +): AbortedInitConfig | undefined { + // Only intended to be called by the `init-post` action. + if (action.name !== ActionName.InitPost) { + return undefined; + } + + return readConfigFile(action.logger, getPathToFailedConfigFile(tempDir)); +} + /** * Generate a `qlconfig.yml` file from the `registries` input. * This file is used by the CodeQL CLI to list the registries to use for each diff --git a/src/config/action-config.ts b/src/config/action-config.ts index fcb6a9482..1024c6fec 100644 --- a/src/config/action-config.ts +++ b/src/config/action-config.ts @@ -142,3 +142,14 @@ export type ActionConfigProperties = keyof Config; export type ActionConfig< T extends ActionConfigProperties = ActionConfigProperties, > = Partial & Pick; + +/** A minimal configuration that we can make available even when `init` aborts. */ +export type AbortedInitConfig = ActionConfig< + | "version" + | "tempDir" + | "gitHubVersion" + | "debugMode" + | "languages" + | "analysisKinds" + | "codeQLCmd" +>; diff --git a/src/init-action.ts b/src/init-action.ts index 3ede80250..a91562611 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -456,6 +456,18 @@ async function run( await checkInstallPython311(config.languages, codeql); } catch (unwrappedError) { + // If we have initialised the Action state, then try to save a subset of it for use by the `init-post` action + // so that it can try to create and upload additional diagnostic information. + if (config !== undefined) { + try { + configUtils.saveAbortedInitConfig(actionState, config); + } catch (configSaveError) { + logger.warning( + `Unable to save configuration for '${ActionName.InitPost}' step: ${getErrorMessage(configSaveError)}`, + ); + } + } + const error = wrapError(unwrappedError); core.setFailed(error.message); const statusReportBase = await createStatusReportBase(