Store partial configuration when init is aborted

This commit is contained in:
Michael B. Gale
2026-10-12 11:01:31 +01:00
parent fd9b9bab05
commit 653a997aa3
4 changed files with 171 additions and 22 deletions

54
lib/entry-points.js generated
View File

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

View File

@@ -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<Config>,
) {
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<Config | undefined> {
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<Config | undefined> {
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

View File

@@ -142,3 +142,14 @@ export type ActionConfigProperties = keyof Config;
export type ActionConfig<
T extends ActionConfigProperties = ActionConfigProperties,
> = Partial<Config> & Pick<Config, T>;
/** A minimal configuration that we can make available even when `init` aborts. */
export type AbortedInitConfig = ActionConfig<
| "version"
| "tempDir"
| "gitHubVersion"
| "debugMode"
| "languages"
| "analysisKinds"
| "codeQLCmd"
>;

View File

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