diff --git a/lib/analyze-action-post.js b/lib/analyze-action-post.js index 807ac63a78..bff2f88161 100644 --- a/lib/analyze-action-post.js +++ b/lib/analyze-action-post.js @@ -117960,7 +117960,18 @@ async function getConfig(tempDir, logger) { const configString = fs3.readFileSync(configFile, "utf8"); logger.debug("Loaded config:"); logger.debug(configString); - return JSON.parse(configString); + const config = JSON.parse(configString); + if (config.version === void 0) { + throw new ConfigurationError( + `Loaded configuration file, but it does not contain the expected 'version' field.` + ); + } + if (config.version !== getActionVersion()) { + throw new ConfigurationError( + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'` + ); + } + return config; } function appendExtraQueryExclusions(extraQueryExclusions, cliConfig) { const augmentedConfig = cloneObject(cliConfig); diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 33c595fdc5..84b0fc6b83 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -91620,7 +91620,18 @@ async function getConfig(tempDir, logger) { const configString = fs9.readFileSync(configFile, "utf8"); logger.debug("Loaded config:"); logger.debug(configString); - return JSON.parse(configString); + const config = JSON.parse(configString); + if (config.version === void 0) { + throw new ConfigurationError( + `Loaded configuration file, but it does not contain the expected 'version' field.` + ); + } + if (config.version !== getActionVersion()) { + throw new ConfigurationError( + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'` + ); + } + return config; } function appendExtraQueryExclusions(extraQueryExclusions, cliConfig) { const augmentedConfig = cloneObject(cliConfig); diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index 3d47b48127..652e6961f3 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -78962,7 +78962,18 @@ async function getConfig(tempDir, logger) { const configString = fs4.readFileSync(configFile, "utf8"); logger.debug("Loaded config:"); logger.debug(configString); - return JSON.parse(configString); + const config = JSON.parse(configString); + if (config.version === void 0) { + throw new ConfigurationError( + `Loaded configuration file, but it does not contain the expected 'version' field.` + ); + } + if (config.version !== getActionVersion()) { + throw new ConfigurationError( + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'` + ); + } + return config; } function appendExtraQueryExclusions(extraQueryExclusions, cliConfig) { const augmentedConfig = cloneObject(cliConfig); diff --git a/lib/init-action-post.js b/lib/init-action-post.js index 98bf415b09..49473d5263 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -129575,7 +129575,18 @@ async function getConfig(tempDir, logger) { const configString = fs9.readFileSync(configFile, "utf8"); logger.debug("Loaded config:"); logger.debug(configString); - return JSON.parse(configString); + const config = JSON.parse(configString); + if (config.version === void 0) { + throw new ConfigurationError( + `Loaded configuration file, but it does not contain the expected 'version' field.` + ); + } + if (config.version !== getActionVersion()) { + throw new ConfigurationError( + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'` + ); + } + return config; } function appendExtraQueryExclusions(extraQueryExclusions, cliConfig) { const augmentedConfig = cloneObject(cliConfig); diff --git a/lib/init-action.js b/lib/init-action.js index 0f8fc3ec9a..49491873c8 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -87335,6 +87335,7 @@ async function initActionState({ augmentationProperties ); return { + version: getActionVersion(), analysisKinds, languages, buildMode, diff --git a/lib/resolve-environment-action.js b/lib/resolve-environment-action.js index 53101dc2ce..4895e7da3f 100644 --- a/lib/resolve-environment-action.js +++ b/lib/resolve-environment-action.js @@ -78689,7 +78689,18 @@ async function getConfig(tempDir, logger) { const configString = fs3.readFileSync(configFile, "utf8"); logger.debug("Loaded config:"); logger.debug(configString); - return JSON.parse(configString); + const config = JSON.parse(configString); + if (config.version === void 0) { + throw new ConfigurationError( + `Loaded configuration file, but it does not contain the expected 'version' field.` + ); + } + if (config.version !== getActionVersion()) { + throw new ConfigurationError( + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'` + ); + } + return config; } function appendExtraQueryExclusions(extraQueryExclusions, cliConfig) { const augmentedConfig = cloneObject(cliConfig); diff --git a/lib/start-proxy-action-post.js b/lib/start-proxy-action-post.js index 8bb47e5626..f8703b67dd 100644 --- a/lib/start-proxy-action-post.js +++ b/lib/start-proxy-action-post.js @@ -117369,7 +117369,18 @@ async function getConfig(tempDir, logger) { const configString = fs.readFileSync(configFile, "utf8"); logger.debug("Loaded config:"); logger.debug(configString); - return JSON.parse(configString); + const config = JSON.parse(configString); + if (config.version === void 0) { + throw new ConfigurationError( + `Loaded configuration file, but it does not contain the expected 'version' field.` + ); + } + if (config.version !== getActionVersion()) { + throw new ConfigurationError( + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'` + ); + } + return config; } // src/debug-artifacts.ts diff --git a/lib/upload-lib.js b/lib/upload-lib.js index d45968cd0a..977b906333 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -89403,7 +89403,18 @@ async function getConfig(tempDir, logger) { const configString = fs7.readFileSync(configFile, "utf8"); logger.debug("Loaded config:"); logger.debug(configString); - return JSON.parse(configString); + const config = JSON.parse(configString); + if (config.version === void 0) { + throw new ConfigurationError( + `Loaded configuration file, but it does not contain the expected 'version' field.` + ); + } + if (config.version !== getActionVersion()) { + throw new ConfigurationError( + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'` + ); + } + return config; } function appendExtraQueryExclusions(extraQueryExclusions, cliConfig) { const augmentedConfig = cloneObject(cliConfig); diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index 7fbbcb3dc2..304b74a261 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -89677,7 +89677,18 @@ async function getConfig(tempDir, logger) { const configString = fs8.readFileSync(configFile, "utf8"); logger.debug("Loaded config:"); logger.debug(configString); - return JSON.parse(configString); + const config = JSON.parse(configString); + if (config.version === void 0) { + throw new ConfigurationError( + `Loaded configuration file, but it does not contain the expected 'version' field.` + ); + } + if (config.version !== getActionVersion()) { + throw new ConfigurationError( + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'` + ); + } + return config; } function appendExtraQueryExclusions(extraQueryExclusions, cliConfig) { const augmentedConfig = cloneObject(cliConfig); diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 3bb9e1e116..f2aa21d121 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -199,6 +199,7 @@ test("load code quality config", async (t) => { // And the config we expect it to result in const expectedConfig: configUtils.Config = { + version: actionsUtil.getActionVersion(), analysisKinds: [AnalysisKind.CodeQuality], languages: [KnownLanguage.actions], buildMode: undefined, @@ -273,6 +274,55 @@ test("loading config saves config", async (t) => { }); }); +test("loading config with version mismatch throws", async (t) => { + return await withTmpDir(async (tempDir) => { + const logger = getRunnerLogger(true); + + const codeql = createStubCodeQL({ + async betterResolveLanguages() { + return { + extractors: { + javascript: [{ extractor_root: "" }], + python: [{ extractor_root: "" }], + }, + }; + }, + }); + + // Sanity check the saved config file does not already exist + t.false(fs.existsSync(configUtils.getPathToParsedConfigFile(tempDir))); + + // Sanity check that getConfig returns undefined before we have called initConfig + t.deepEqual(await configUtils.getConfig(tempDir, logger), undefined); + + // Stub `getActionVersion` to return some nonsense. + const getActionVersionStub = sinon + .stub(actionsUtil, "getActionVersion") + .returns("does-not-exist"); + + await configUtils.initConfig( + createTestInitConfigInputs({ + languagesInput: "javascript,python", + tempDir, + codeql, + workspacePath: tempDir, + logger, + }), + ); + + // Restore `getActionVersion`. + getActionVersionStub.restore(); + + // The saved config file should now exist + t.true(fs.existsSync(configUtils.getPathToParsedConfigFile(tempDir))); + + // Trying to read the configuration should now throw an error. + await t.throwsAsync(configUtils.getConfig(tempDir, logger), { + instanceOf: ConfigurationError, + }); + }); +}); + test("load input outside of workspace", async (t) => { return await withTmpDir(async (tempDir) => { try { @@ -389,6 +439,7 @@ test("load non-empty input", async (t) => { // And the config we expect it to parse to const expectedConfig: configUtils.Config = { + version: actionsUtil.getActionVersion(), analysisKinds: [AnalysisKind.CodeScanning], languages: [KnownLanguage.javascript], buildMode: BuildMode.None, diff --git a/src/config-utils.ts b/src/config-utils.ts index 92d36920c8..7bdc29a2e2 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -5,7 +5,7 @@ import { performance } from "perf_hooks"; import * as yaml from "js-yaml"; import * as semver from "semver"; -import { isAnalyzingPullRequest } from "./actions-util"; +import { getActionVersion, isAnalyzingPullRequest } from "./actions-util"; import { AnalysisConfig, AnalysisKind, @@ -102,6 +102,10 @@ interface IncludeQueryFilter { * Format of the parsed config file. */ export interface Config { + /** + * The version of the CodeQL Action that the configuration is for. + */ + version: string; /** * Set of analysis kinds that are enabled. */ @@ -591,6 +595,7 @@ export async function initActionState( ); return { + version: getActionVersion(), analysisKinds, languages, buildMode, @@ -1308,7 +1313,21 @@ export async function getConfig( const configString = fs.readFileSync(configFile, "utf8"); logger.debug("Loaded config:"); logger.debug(configString); - return JSON.parse(configString) as Config; + + const config = JSON.parse(configString) as Partial; + + if (config.version === undefined) { + throw new ConfigurationError( + `Loaded configuration file, but it does not contain the expected 'version' field.`, + ); + } + if (config.version !== getActionVersion()) { + throw new ConfigurationError( + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'`, + ); + } + + return config as Config; } /** diff --git a/src/testing-utils.ts b/src/testing-utils.ts index a12c995851..c930d5350c 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -6,6 +6,7 @@ import { TestFn } from "ava"; import nock from "nock"; import * as sinon from "sinon"; +import { getActionVersion } from "./actions-util"; import { AnalysisKind } from "./analyses"; import * as apiClient from "./api-client"; import { GitHubApiDetails } from "./api-client"; @@ -356,6 +357,7 @@ export function createTestConfig(overrides: Partial): Config { return Object.assign( {}, { + version: getActionVersion(), analysisKinds: [AnalysisKind.CodeScanning], languages: [], buildMode: undefined,