Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions lib/init-action-post.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 21 additions & 10 deletions lib/init-action.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
15 changes: 4 additions & 11 deletions src/init-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
88 changes: 86 additions & 2 deletions src/workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -13,6 +21,7 @@ import {
Workflow,
WorkflowErrors,
} from "./workflow";
import * as workflow from "./workflow";

function errorCodes(
actual: CodedError[],
Expand Down Expand Up @@ -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);
});
37 changes: 36 additions & 1 deletion src/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<undefined | string> {
Expand Down Expand Up @@ -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,
};
Loading