diff --git a/lib/init-action-post.js b/lib/init-action-post.js index ba2c283386..0ed7b0dd62 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -19755,22 +19755,22 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); process.stdout.write(message + os3.EOL); } exports2.info = info5; - function startGroup3(name) { + function startGroup4(name) { (0, command_1.issue)("group", name); } - exports2.startGroup = startGroup3; - function endGroup3() { + exports2.startGroup = startGroup4; + function endGroup4() { (0, command_1.issue)("endgroup"); } - exports2.endGroup = endGroup3; + exports2.endGroup = endGroup4; function group(name, fn) { return __awaiter4(this, void 0, void 0, function* () { - startGroup3(name); + startGroup4(name); let result; try { result = yield fn(); } finally { - endGroup3(); + endGroup4(); } return result; }); diff --git a/lib/init-action.js b/lib/init-action.js index 202465611f..f82412930e 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -92208,6 +92208,26 @@ async function getWorkflowAbsolutePath(logger) { `Expected to find a code scanning workflow file at ${absolutePath}, but no such file existed. This can happen if the currently running workflow checks out a branch that doesn't contain the corresponding workflow file.` ); } +async function checkWorkflow(logger, codeql) { + if (!isDynamicWorkflow() && process.env["CODEQL_ACTION_SKIP_WORKFLOW_VALIDATION" /* SKIP_WORKFLOW_VALIDATION */] !== "true") { + core12.startGroup("Validating workflow"); + const validateWorkflowResult = await internal.validateWorkflow( + codeql, + logger + ); + if (validateWorkflowResult === void 0) { + logger.info("Detected no issues with the code scanning workflow."); + } else { + logger.debug( + `Unable to validate code scanning workflow: ${validateWorkflowResult}` + ); + } + core12.endGroup(); + } +} +var internal = { + validateWorkflow +}; // src/init-action.ts async function sendStartingStatusReport(startedAt, config, logger) { @@ -92345,16 +92365,7 @@ async function run() { toolsVersion = initCodeQLResult.toolsVersion; toolsSource = initCodeQLResult.toolsSource; zstdAvailability = initCodeQLResult.zstdAvailability; - core13.startGroup("Validating workflow"); - const validateWorkflowResult = await validateWorkflow(codeql, logger); - if (validateWorkflowResult === void 0) { - logger.info("Detected no issues with the code scanning workflow."); - } else { - logger.warning( - `Unable to validate code scanning workflow: ${validateWorkflowResult}` - ); - } - core13.endGroup(); + await checkWorkflow(logger, codeql); if ( // Only enable the experimental features env variable for Rust analysis if the user has explicitly // requested rust - don't enable it via language autodetection. diff --git a/src/environment.ts b/src/environment.ts index 6986641222..16a016aaaa 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -137,4 +137,10 @@ export enum EnvVar { * This setting is more specific than `CODEQL_ACTION_TEST_MODE`, which implies this option. */ SKIP_SARIF_UPLOAD = "CODEQL_ACTION_SKIP_SARIF_UPLOAD", + + /** + * Whether to skip workflow validation. Intended for internal use, where we know that + * the workflow is valid and validation is not necessary. + */ + SKIP_WORKFLOW_VALIDATION = "CODEQL_ACTION_SKIP_WORKFLOW_VALIDATION", } diff --git a/src/init-action.ts b/src/init-action.ts index 8961b09f4f..97cb6b784e 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -86,7 +86,7 @@ import { getErrorMessage, BuildMode, } from "./util"; -import { validateWorkflow } from "./workflow"; +import { checkWorkflow } from "./workflow"; /** * Sends a status report indicating that the `init` Action is starting. @@ -288,16 +288,9 @@ async function run() { toolsSource = initCodeQLResult.toolsSource; zstdAvailability = initCodeQLResult.zstdAvailability; - core.startGroup("Validating workflow"); - const validateWorkflowResult = await validateWorkflow(codeql, logger); - if (validateWorkflowResult === undefined) { - logger.info("Detected no issues with the code scanning workflow."); - } else { - logger.warning( - `Unable to validate code scanning workflow: ${validateWorkflowResult}`, - ); - } - core.endGroup(); + // Check the workflow for problems. If there are any problems, they are reported + // to the workflow log. No exceptions are thrown. + await checkWorkflow(logger, codeql); // Set CODEQL_ENABLE_EXPERIMENTAL_FEATURES for Rust if between 2.19.3 (included) and 2.22.1 (excluded) // We need to set this environment variable before initializing the config, otherwise Rust diff --git a/src/workflow.test.ts b/src/workflow.test.ts index e922d8079c..f05ad54851 100644 --- a/src/workflow.test.ts +++ b/src/workflow.test.ts @@ -2,9 +2,17 @@ import test, { ExecutionContext } from "ava"; import * as yaml from "js-yaml"; import * as sinon from "sinon"; -import { getCodeQLForTesting } from "./codeql"; -import { setupTests } from "./testing-utils"; +import * as actionsUtil from "./actions-util"; +import { createStubCodeQL, getCodeQLForTesting } from "./codeql"; +import { EnvVar } from "./environment"; import { + checkExpectedLogMessages, + getRecordingLogger, + LoggedMessage, + setupTests, +} from "./testing-utils"; +import { + checkWorkflow, CodedError, formatWorkflowCause, formatWorkflowErrors, @@ -13,6 +21,7 @@ import { Workflow, WorkflowErrors, } from "./workflow"; +import * as workflow from "./workflow"; function errorCodes( actual: CodedError[], @@ -870,3 +879,78 @@ test("getCategoryInputOrThrow throws error for workflow with multiple calls to a }, ); }); + +test("checkWorkflow - validates workflow if `SKIP_WORKFLOW_VALIDATION` is not set", async (t) => { + const messages: LoggedMessage[] = []; + const codeql = createStubCodeQL({}); + + sinon.stub(actionsUtil, "isDynamicWorkflow").returns(false); + const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow"); + validateWorkflow.resolves(undefined); + + await checkWorkflow(getRecordingLogger(messages), codeql); + + t.assert( + validateWorkflow.calledOnce, + "`checkWorkflow` unexpectedly did not call `validateWorkflow`", + ); + checkExpectedLogMessages(t, messages, [ + "Detected no issues with the code scanning workflow.", + ]); +}); + +test("checkWorkflow - logs problems with workflow validation", async (t) => { + const messages: LoggedMessage[] = []; + const codeql = createStubCodeQL({}); + + sinon.stub(actionsUtil, "isDynamicWorkflow").returns(false); + const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow"); + validateWorkflow.resolves("problem"); + + await checkWorkflow(getRecordingLogger(messages), codeql); + + t.assert( + validateWorkflow.calledOnce, + "`checkWorkflow` unexpectedly did not call `validateWorkflow`", + ); + checkExpectedLogMessages(t, messages, [ + "Unable to validate code scanning workflow: problem", + ]); +}); + +test("checkWorkflow - skips validation if `SKIP_WORKFLOW_VALIDATION` is `true`", async (t) => { + process.env[EnvVar.SKIP_WORKFLOW_VALIDATION] = "true"; + + const messages: LoggedMessage[] = []; + const codeql = createStubCodeQL({}); + + sinon.stub(actionsUtil, "isDynamicWorkflow").returns(false); + const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow"); + + await checkWorkflow(getRecordingLogger(messages), codeql); + + t.assert( + validateWorkflow.notCalled, + "`checkWorkflow` called `validateWorkflow` unexpectedly", + ); + t.is(messages.length, 0); +}); + +test("checkWorkflow - skips validation for `dynamic` workflows", async (t) => { + const messages: LoggedMessage[] = []; + const codeql = createStubCodeQL({}); + + const isDynamicWorkflow = sinon + .stub(actionsUtil, "isDynamicWorkflow") + .returns(true); + const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow"); + + await checkWorkflow(getRecordingLogger(messages), codeql); + + t.assert(isDynamicWorkflow.calledOnce); + t.assert( + validateWorkflow.notCalled, + "`checkWorkflow` called `validateWorkflow` unexpectedly", + ); + t.is(messages.length, 0); +}); diff --git a/src/workflow.ts b/src/workflow.ts index adcb22baec..467980fb0a 100644 --- a/src/workflow.ts +++ b/src/workflow.ts @@ -5,8 +5,10 @@ import zlib from "zlib"; import * as core from "@actions/core"; import * as yaml from "js-yaml"; +import { isDynamicWorkflow } from "./actions-util"; import * as api from "./api-client"; import { CodeQL } from "./codeql"; +import { EnvVar } from "./environment"; import { Logger } from "./logging"; import { getRequiredEnvParam, @@ -216,7 +218,7 @@ function hasWorkflowTrigger(triggerName: string, doc: Workflow): boolean { return Object.prototype.hasOwnProperty.call(doc.on, triggerName); } -export async function validateWorkflow( +async function validateWorkflow( codeql: CodeQL, logger: Logger, ): Promise { @@ -462,3 +464,36 @@ export function getCheckoutPathInputOrThrow( ) || getRequiredEnvParam("GITHUB_WORKSPACE") // if unspecified, checkout_path defaults to ${{ github.workspace }} ); } + +/** + * A wrapper around `validateWorkflow` which reports the outcome. + * + * @param logger The logger to use. + * @param codeql The CodeQL instance. + */ +export async function checkWorkflow(logger: Logger, codeql: CodeQL) { + // Check the workflow for problems, unless `SKIP_WORKFLOW_VALIDATION` is `true` + // or the workflow trigger is `dynamic`. + if ( + !isDynamicWorkflow() && + process.env[EnvVar.SKIP_WORKFLOW_VALIDATION] !== "true" + ) { + core.startGroup("Validating workflow"); + const validateWorkflowResult = await internal.validateWorkflow( + codeql, + logger, + ); + if (validateWorkflowResult === undefined) { + logger.info("Detected no issues with the code scanning workflow."); + } else { + logger.debug( + `Unable to validate code scanning workflow: ${validateWorkflowResult}`, + ); + } + core.endGroup(); + } +} + +export const internal = { + validateWorkflow, +};