Merge pull request #1420 from github/henrymercer/failed-runs-fix-action-not-found

Fix failed SARIF upload behavior when the workflow doesn't call the CodeQL Action
This commit is contained in:
Henry Mercer 2022-12-07 08:48:11 +00:00 committed by GitHub
commit 79166d0788
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 219 additions and 136 deletions

View file

@ -73,7 +73,7 @@ async function run(uploadDatabaseBundleDebugArtifact, uploadLogsDebugArtifact, p
throw new Error("Expected to upload a SARIF file for the failed run, but encountered " +
`the following error: ${e}`);
}
logger.warning(`Failed to upload a SARIF file for the failed run. Error: ${e}`);
logger.info(`Failed to upload a SARIF file for the failed run. Error: ${e}`);
}
}
else if (expectFailedSarifUpload) {

View file

@ -1 +1 @@
{"version":3,"file":"init-action-post-helper.js","sourceRoot":"","sources":["../src/init-action-post-helper.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"init-action-post-helper.js","sourceRoot":"","sources":["../src/init-action-post-helper.ts"],"names":[],"mappings":"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"}

View file

@ -38,6 +38,7 @@ const workflow = __importStar(require("./workflow"));
(0, testing_utils_1.setupTests)(ava_1.default);
(0, ava_1.default)("post: init action with debug mode off", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
process.env["RUNNER_TEMP"] = tmpDir;
const gitHubVersion = {
type: util.GitHubVariant.DOTCOM,
@ -59,6 +60,7 @@ const workflow = __importStar(require("./workflow"));
});
(0, ava_1.default)("post: init action with debug mode on", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
process.env["RUNNER_TEMP"] = tmpDir;
const gitHubVersion = {
type: util.GitHubVariant.DOTCOM,
@ -79,21 +81,39 @@ const workflow = __importStar(require("./workflow"));
});
});
(0, ava_1.default)("uploads failed SARIF run for typical workflow", async (t) => {
const config = {
codeQLCmd: "codeql",
debugMode: true,
languages: [],
packs: [],
};
const messages = [];
process.env["GITHUB_JOB"] = "analyze";
process.env["GITHUB_WORKSPACE"] =
"/home/runner/work/codeql-action/codeql-action";
sinon.stub(actionsUtil, "getRequiredInput").withArgs("matrix").returns("{}");
const codeqlObject = await codeql.getCodeQLForTesting();
sinon.stub(codeql, "getCodeQL").resolves(codeqlObject);
const diagnosticsExportStub = sinon.stub(codeqlObject, "diagnosticsExport");
sinon.stub(workflow, "getWorkflow").resolves({
const actionsWorkflow = createTestWorkflow([
{
name: "Checkout repository",
uses: "actions/checkout@v3",
},
{
name: "Initialize CodeQL",
uses: "github/codeql-action/init@v2",
with: {
languages: "javascript",
},
},
{
name: "Perform CodeQL Analysis",
uses: "github/codeql-action/analyze@v2",
with: {
category: "my-category",
},
},
]);
await testFailedSarifUpload(t, actionsWorkflow, { category: "my-category" });
});
(0, ava_1.default)("uploading failed SARIF run fails when workflow does not reference github/codeql-action", async (t) => {
const actionsWorkflow = createTestWorkflow([
{
name: "Checkout repository",
uses: "actions/checkout@v3",
},
]);
await t.throwsAsync(async () => await testFailedSarifUpload(t, actionsWorkflow));
});
function createTestWorkflow(steps) {
return {
name: "CodeQL",
on: {
push: {
@ -107,38 +127,37 @@ const workflow = __importStar(require("./workflow"));
analyze: {
name: "CodeQL Analysis",
"runs-on": "ubuntu-latest",
steps: [
{
name: "Checkout repository",
uses: "actions/checkout@v3",
},
{
name: "Initialize CodeQL",
uses: "github/codeql-action/init@v2",
with: {
languages: "javascript",
},
},
{
name: "Perform CodeQL Analysis",
uses: "github/codeql-action/analyze@v2",
with: {
category: "my-category",
},
},
],
steps,
},
},
});
};
}
async function testFailedSarifUpload(t, actionsWorkflow, { category } = {}) {
const config = {
codeQLCmd: "codeql",
debugMode: true,
languages: [],
packs: [],
};
const messages = [];
process.env["GITHUB_JOB"] = "analyze";
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
process.env["GITHUB_WORKSPACE"] =
"/home/runner/work/codeql-action/codeql-action";
sinon.stub(actionsUtil, "getRequiredInput").withArgs("matrix").returns("{}");
const codeqlObject = await codeql.getCodeQLForTesting();
sinon.stub(codeql, "getCodeQL").resolves(codeqlObject);
const diagnosticsExportStub = sinon.stub(codeqlObject, "diagnosticsExport");
sinon.stub(workflow, "getWorkflow").resolves(actionsWorkflow);
const uploadFromActions = sinon.stub(uploadLib, "uploadFromActions");
uploadFromActions.resolves({ sarifID: "42" });
const waitForProcessing = sinon.stub(uploadLib, "waitForProcessing");
await initActionPostHelper.uploadFailedSarif(config, (0, repository_1.parseRepositoryNwo)("github/codeql-action"), (0, testing_utils_1.createFeatures)([feature_flags_1.Feature.UploadFailedSarifEnabled]), (0, testing_utils_1.getRecordingLogger)(messages));
t.deepEqual(messages, []);
t.true(diagnosticsExportStub.calledOnceWith(sinon.match.string, "my-category"));
t.true(uploadFromActions.calledOnceWith(sinon.match.string, sinon.match.string, "my-category", sinon.match.any));
t.true(diagnosticsExportStub.calledOnceWith(sinon.match.string, category), `Actual args were: ${diagnosticsExportStub.args}`);
t.true(uploadFromActions.calledOnceWith(sinon.match.string, sinon.match.string, category, sinon.match.any), `Actual args were: ${uploadFromActions.args}`);
t.true(waitForProcessing.calledOnceWith(sinon.match.any, "42", sinon.match.any, {
isUnsuccessfulExecution: true,
}));
});
}
//# sourceMappingURL=init-action-post-helper.test.js.map

File diff suppressed because one or more lines are too long

45
lib/workflow.js generated
View file

@ -264,25 +264,23 @@ function getStepsCallingAction(job, actionName) {
* determine that no such input is passed to the Action.
*/
function getInputOrThrow(workflow, jobName, actionName, inputName, matrixVars) {
var _a;
const preamble = `Could not get ${inputName} input to ${actionName} since`;
if (!workflow.jobs) {
throw new Error(`Could not get ${inputName} input to ${actionName} since the workflow has no jobs.`);
throw new Error(`${preamble} the workflow has no jobs.`);
}
if (!workflow.jobs[jobName]) {
throw new Error(`Could not get ${inputName} input to ${actionName} since the workflow has no job named ${jobName}.`);
throw new Error(`${preamble} the workflow has no job named ${jobName}.`);
}
const inputs = getStepsCallingAction(workflow.jobs[jobName], actionName)
.map((step) => { var _a; return (_a = step.with) === null || _a === void 0 ? void 0 : _a[inputName]; })
.filter((input) => input !== undefined)
.map((input) => input);
if (inputs.length === 0) {
return undefined;
const stepsCallingAction = getStepsCallingAction(workflow.jobs[jobName], actionName);
if (stepsCallingAction.length === 0) {
throw new Error(`${preamble} the ${jobName} job does not call ${actionName}.`);
}
if (!inputs.every((input) => input === inputs[0])) {
throw new Error(`Could not get ${inputName} input to ${actionName} since there were multiple steps calling ` +
`${actionName} with different values for ${inputName}.`);
else if (stepsCallingAction.length > 1) {
throw new Error(`${preamble} the ${jobName} job calls ${actionName} multiple times.`);
}
let input = inputs[0];
if (matrixVars !== undefined) {
let input = (_a = stepsCallingAction[0].with) === null || _a === void 0 ? void 0 : _a[inputName];
if (input !== undefined && matrixVars !== undefined) {
// Make a basic attempt to substitute matrix variables
// First normalize by removing whitespace
input = input.replace(/\${{\s+/, "${{").replace(/\s+}}/, "}}");
@ -290,11 +288,24 @@ function getInputOrThrow(workflow, jobName, actionName, inputName, matrixVars) {
input = input.replace(`\${{matrix.${key}}}`, value);
}
}
if (input.includes("${{")) {
if (input !== undefined && input.includes("${{")) {
throw new Error(`Could not get ${inputName} input to ${actionName} since it contained an unrecognized dynamic value.`);
}
return input;
}
/**
* Get the expected name of the analyze Action.
*
* This allows us to test workflow parsing functionality as a CodeQL Action PR check.
*/
function getAnalyzeActionName() {
if ((0, util_1.getRequiredEnvParam)("GITHUB_REPOSITORY") === "github/codeql-action") {
return "./analyze";
}
else {
return "github/codeql-action/analyze";
}
}
/**
* Makes a best effort attempt to retrieve the category input for the particular job,
* given a set of matrix variables.
@ -305,7 +316,7 @@ function getInputOrThrow(workflow, jobName, actionName, inputName, matrixVars) {
* @throws an error if the category input could not be determined
*/
function getCategoryInputOrThrow(workflow, jobName, matrixVars) {
return getInputOrThrow(workflow, jobName, "github/codeql-action/analyze", "category", matrixVars);
return getInputOrThrow(workflow, jobName, getAnalyzeActionName(), "category", matrixVars);
}
exports.getCategoryInputOrThrow = getCategoryInputOrThrow;
/**
@ -318,7 +329,7 @@ exports.getCategoryInputOrThrow = getCategoryInputOrThrow;
* @throws an error if the upload input could not be determined
*/
function getUploadInputOrThrow(workflow, jobName, matrixVars) {
return (getInputOrThrow(workflow, jobName, "github/codeql-action/analyze", "upload", matrixVars) || "true" // if unspecified, upload defaults to true
return (getInputOrThrow(workflow, jobName, getAnalyzeActionName(), "upload", matrixVars) || "true" // if unspecified, upload defaults to true
);
}
exports.getUploadInputOrThrow = getUploadInputOrThrow;
@ -332,7 +343,7 @@ exports.getUploadInputOrThrow = getUploadInputOrThrow;
* @throws an error if the checkout_path input could not be determined
*/
function getCheckoutPathInputOrThrow(workflow, jobName, matrixVars) {
return (getInputOrThrow(workflow, jobName, "github/codeql-action/analyze", "checkout_path", matrixVars) || (0, util_1.getRequiredEnvParam)("GITHUB_WORKSPACE") // if unspecified, checkout_path defaults to ${{ github.workspace }}
return (getInputOrThrow(workflow, jobName, getAnalyzeActionName(), "checkout_path", matrixVars) || (0, util_1.getRequiredEnvParam)("GITHUB_WORKSPACE") // if unspecified, checkout_path defaults to ${{ github.workspace }}
);
}
exports.getCheckoutPathInputOrThrow = getCheckoutPathInputOrThrow;

File diff suppressed because one or more lines are too long

12
lib/workflow.test.js generated
View file

@ -356,6 +356,7 @@ function errorCodes(actual, expected) {
`)), []));
});
(0, ava_1.default)("getCategoryInputOrThrow returns category for simple workflow with category", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.is((0, workflow_1.getCategoryInputOrThrow)(yaml.load(`
jobs:
analysis:
@ -369,6 +370,7 @@ function errorCodes(actual, expected) {
`), "analysis", {}), "some-category");
});
(0, ava_1.default)("getCategoryInputOrThrow returns undefined for simple workflow without category", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.is((0, workflow_1.getCategoryInputOrThrow)(yaml.load(`
jobs:
analysis:
@ -380,6 +382,7 @@ function errorCodes(actual, expected) {
`), "analysis", {}), undefined);
});
(0, ava_1.default)("getCategoryInputOrThrow returns category for workflow with multiple jobs", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.is((0, workflow_1.getCategoryInputOrThrow)(yaml.load(`
jobs:
foo:
@ -403,6 +406,7 @@ function errorCodes(actual, expected) {
`), "bar", {}), "bar-category");
});
(0, ava_1.default)("getCategoryInputOrThrow finds category for workflow with language matrix", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.is((0, workflow_1.getCategoryInputOrThrow)(yaml.load(`
jobs:
analysis:
@ -421,6 +425,7 @@ function errorCodes(actual, expected) {
`), "analysis", { language: "javascript" }), "/language:javascript");
});
(0, ava_1.default)("getCategoryInputOrThrow throws error for workflow with dynamic category", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.throws(() => (0, workflow_1.getCategoryInputOrThrow)(yaml.load(`
jobs:
analysis:
@ -435,7 +440,8 @@ function errorCodes(actual, expected) {
"an unrecognized dynamic value.",
});
});
(0, ava_1.default)("getCategoryInputOrThrow throws error for workflow with multiple categories", (t) => {
(0, ava_1.default)("getCategoryInputOrThrow throws error for workflow with multiple calls to analyze", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.throws(() => (0, workflow_1.getCategoryInputOrThrow)(yaml.load(`
jobs:
analysis:
@ -450,8 +456,8 @@ function errorCodes(actual, expected) {
with:
category: another-category
`), "analysis", {}), {
message: "Could not get category input to github/codeql-action/analyze since there were multiple steps " +
"calling github/codeql-action/analyze with different values for category.",
message: "Could not get category input to github/codeql-action/analyze since the analysis job " +
"calls github/codeql-action/analyze multiple times.",
});
});
//# sourceMappingURL=workflow.test.js.map

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
import test from "ava";
import test, { ExecutionContext } from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
@ -21,6 +21,7 @@ setupTests(test);
test("post: init action with debug mode off", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
process.env["RUNNER_TEMP"] = tmpDir;
const gitHubVersion: util.GitHubVersion = {
@ -54,6 +55,7 @@ test("post: init action with debug mode off", async (t) => {
test("post: init action with debug mode on", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
process.env["RUNNER_TEMP"] = tmpDir;
const gitHubVersion: util.GitHubVersion = {
@ -86,23 +88,45 @@ test("post: init action with debug mode on", async (t) => {
});
test("uploads failed SARIF run for typical workflow", async (t) => {
const config = {
codeQLCmd: "codeql",
debugMode: true,
languages: [],
packs: [],
} as unknown as configUtils.Config;
const messages = [];
process.env["GITHUB_JOB"] = "analyze";
process.env["GITHUB_WORKSPACE"] =
"/home/runner/work/codeql-action/codeql-action";
sinon.stub(actionsUtil, "getRequiredInput").withArgs("matrix").returns("{}");
const actionsWorkflow = createTestWorkflow([
{
name: "Checkout repository",
uses: "actions/checkout@v3",
},
{
name: "Initialize CodeQL",
uses: "github/codeql-action/init@v2",
with: {
languages: "javascript",
},
},
{
name: "Perform CodeQL Analysis",
uses: "github/codeql-action/analyze@v2",
with: {
category: "my-category",
},
},
]);
await testFailedSarifUpload(t, actionsWorkflow, { category: "my-category" });
});
const codeqlObject = await codeql.getCodeQLForTesting();
sinon.stub(codeql, "getCodeQL").resolves(codeqlObject);
const diagnosticsExportStub = sinon.stub(codeqlObject, "diagnosticsExport");
test("uploading failed SARIF run fails when workflow does not reference github/codeql-action", async (t) => {
const actionsWorkflow = createTestWorkflow([
{
name: "Checkout repository",
uses: "actions/checkout@v3",
},
]);
await t.throwsAsync(
async () => await testFailedSarifUpload(t, actionsWorkflow)
);
});
sinon.stub(workflow, "getWorkflow").resolves({
function createTestWorkflow(
steps: workflow.WorkflowJobStep[]
): workflow.Workflow {
return {
name: "CodeQL",
on: {
push: {
@ -116,29 +140,35 @@ test("uploads failed SARIF run for typical workflow", async (t) => {
analyze: {
name: "CodeQL Analysis",
"runs-on": "ubuntu-latest",
steps: [
{
name: "Checkout repository",
uses: "actions/checkout@v3",
},
{
name: "Initialize CodeQL",
uses: "github/codeql-action/init@v2",
with: {
languages: "javascript",
},
},
{
name: "Perform CodeQL Analysis",
uses: "github/codeql-action/analyze@v2",
with: {
category: "my-category",
},
},
],
steps,
},
},
});
};
}
async function testFailedSarifUpload(
t: ExecutionContext<unknown>,
actionsWorkflow: workflow.Workflow,
{ category }: { category?: string } = {}
): Promise<void> {
const config = {
codeQLCmd: "codeql",
debugMode: true,
languages: [],
packs: [],
} as unknown as configUtils.Config;
const messages = [];
process.env["GITHUB_JOB"] = "analyze";
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
process.env["GITHUB_WORKSPACE"] =
"/home/runner/work/codeql-action/codeql-action";
sinon.stub(actionsUtil, "getRequiredInput").withArgs("matrix").returns("{}");
const codeqlObject = await codeql.getCodeQLForTesting();
sinon.stub(codeql, "getCodeQL").resolves(codeqlObject);
const diagnosticsExportStub = sinon.stub(codeqlObject, "diagnosticsExport");
sinon.stub(workflow, "getWorkflow").resolves(actionsWorkflow);
const uploadFromActions = sinon.stub(uploadLib, "uploadFromActions");
uploadFromActions.resolves({ sarifID: "42" } as uploadLib.UploadResult);
@ -152,19 +182,21 @@ test("uploads failed SARIF run for typical workflow", async (t) => {
);
t.deepEqual(messages, []);
t.true(
diagnosticsExportStub.calledOnceWith(sinon.match.string, "my-category")
diagnosticsExportStub.calledOnceWith(sinon.match.string, category),
`Actual args were: ${diagnosticsExportStub.args}`
);
t.true(
uploadFromActions.calledOnceWith(
sinon.match.string,
sinon.match.string,
"my-category",
category,
sinon.match.any
)
),
`Actual args were: ${uploadFromActions.args}`
);
t.true(
waitForProcessing.calledOnceWith(sinon.match.any, "42", sinon.match.any, {
isUnsuccessfulExecution: true,
})
);
});
}

View file

@ -101,7 +101,7 @@ export async function run(
`the following error: ${e}`
);
}
logger.warning(
logger.info(
`Failed to upload a SARIF file for the failed run. Error: ${e}`
);
}

View file

@ -525,6 +525,7 @@ test("getWorkflowErrors() should not report an error if PRs are totally unconfig
});
test("getCategoryInputOrThrow returns category for simple workflow with category", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.is(
getCategoryInputOrThrow(
yaml.load(`
@ -546,6 +547,7 @@ test("getCategoryInputOrThrow returns category for simple workflow with category
});
test("getCategoryInputOrThrow returns undefined for simple workflow without category", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.is(
getCategoryInputOrThrow(
yaml.load(`
@ -565,6 +567,7 @@ test("getCategoryInputOrThrow returns undefined for simple workflow without cate
});
test("getCategoryInputOrThrow returns category for workflow with multiple jobs", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.is(
getCategoryInputOrThrow(
yaml.load(`
@ -596,6 +599,7 @@ test("getCategoryInputOrThrow returns category for workflow with multiple jobs",
});
test("getCategoryInputOrThrow finds category for workflow with language matrix", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.is(
getCategoryInputOrThrow(
yaml.load(`
@ -622,6 +626,7 @@ test("getCategoryInputOrThrow finds category for workflow with language matrix",
});
test("getCategoryInputOrThrow throws error for workflow with dynamic category", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.throws(
() =>
getCategoryInputOrThrow(
@ -646,7 +651,8 @@ test("getCategoryInputOrThrow throws error for workflow with dynamic category",
);
});
test("getCategoryInputOrThrow throws error for workflow with multiple categories", (t) => {
test("getCategoryInputOrThrow throws error for workflow with multiple calls to analyze", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.throws(
() =>
getCategoryInputOrThrow(
@ -669,8 +675,8 @@ test("getCategoryInputOrThrow throws error for workflow with multiple categories
),
{
message:
"Could not get category input to github/codeql-action/analyze since there were multiple steps " +
"calling github/codeql-action/analyze with different values for category.",
"Could not get category input to github/codeql-action/analyze since the analysis job " +
"calls github/codeql-action/analyze multiple times.",
}
);
});

View file

@ -7,7 +7,7 @@ import * as yaml from "js-yaml";
import * as api from "./api-client";
import { getRequiredEnvParam } from "./util";
interface WorkflowJobStep {
export interface WorkflowJobStep {
name?: string;
run?: any;
uses?: string;
@ -327,35 +327,32 @@ function getInputOrThrow(
inputName: string,
matrixVars: { [key: string]: string } | undefined
) {
const preamble = `Could not get ${inputName} input to ${actionName} since`;
if (!workflow.jobs) {
throw new Error(
`Could not get ${inputName} input to ${actionName} since the workflow has no jobs.`
);
throw new Error(`${preamble} the workflow has no jobs.`);
}
if (!workflow.jobs[jobName]) {
throw new Error(`${preamble} the workflow has no job named ${jobName}.`);
}
const stepsCallingAction = getStepsCallingAction(
workflow.jobs[jobName],
actionName
);
if (stepsCallingAction.length === 0) {
throw new Error(
`Could not get ${inputName} input to ${actionName} since the workflow has no job named ${jobName}.`
`${preamble} the ${jobName} job does not call ${actionName}.`
);
} else if (stepsCallingAction.length > 1) {
throw new Error(
`${preamble} the ${jobName} job calls ${actionName} multiple times.`
);
}
const inputs = getStepsCallingAction(workflow.jobs[jobName], actionName)
.map((step) => step.with?.[inputName])
.filter((input) => input !== undefined)
.map((input) => input!);
let input = stepsCallingAction[0].with?.[inputName];
if (inputs.length === 0) {
return undefined;
}
if (!inputs.every((input) => input === inputs[0])) {
throw new Error(
`Could not get ${inputName} input to ${actionName} since there were multiple steps calling ` +
`${actionName} with different values for ${inputName}.`
);
}
let input = inputs[0];
if (matrixVars !== undefined) {
if (input !== undefined && matrixVars !== undefined) {
// Make a basic attempt to substitute matrix variables
// First normalize by removing whitespace
input = input.replace(/\${{\s+/, "${{").replace(/\s+}}/, "}}");
@ -363,8 +360,7 @@ function getInputOrThrow(
input = input.replace(`\${{matrix.${key}}}`, value);
}
}
if (input.includes("${{")) {
if (input !== undefined && input.includes("${{")) {
throw new Error(
`Could not get ${inputName} input to ${actionName} since it contained an unrecognized dynamic value.`
);
@ -372,6 +368,19 @@ function getInputOrThrow(
return input;
}
/**
* Get the expected name of the analyze Action.
*
* This allows us to test workflow parsing functionality as a CodeQL Action PR check.
*/
function getAnalyzeActionName() {
if (getRequiredEnvParam("GITHUB_REPOSITORY") === "github/codeql-action") {
return "./analyze";
} else {
return "github/codeql-action/analyze";
}
}
/**
* Makes a best effort attempt to retrieve the category input for the particular job,
* given a set of matrix variables.
@ -389,7 +398,7 @@ export function getCategoryInputOrThrow(
return getInputOrThrow(
workflow,
jobName,
"github/codeql-action/analyze",
getAnalyzeActionName(),
"category",
matrixVars
);
@ -413,7 +422,7 @@ export function getUploadInputOrThrow(
getInputOrThrow(
workflow,
jobName,
"github/codeql-action/analyze",
getAnalyzeActionName(),
"upload",
matrixVars
) || "true" // if unspecified, upload defaults to true
@ -438,7 +447,7 @@ export function getCheckoutPathInputOrThrow(
getInputOrThrow(
workflow,
jobName,
"github/codeql-action/analyze",
getAnalyzeActionName(),
"checkout_path",
matrixVars
) || getRequiredEnvParam("GITHUB_WORKSPACE") // if unspecified, checkout_path defaults to ${{ github.workspace }}