Skip to content

Commit

Permalink
Showing 6 changed files with 417 additions and 2 deletions.
81 changes: 81 additions & 0 deletions lib/actions-util.js

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

2 changes: 1 addition & 1 deletion lib/actions-util.js.map

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

95 changes: 95 additions & 0 deletions lib/actions-util.test.js
2 changes: 1 addition & 1 deletion lib/actions-util.test.js.map
123 changes: 123 additions & 0 deletions src/actions-util.test.ts
@@ -81,3 +81,126 @@ test("prepareEnvironment() when a local run", (t) => {
t.deepEqual(process.env.GITHUB_JOB, "UNKNOWN-JOB");
t.deepEqual(process.env.CODEQL_ACTION_ANALYSIS_KEY, "LOCAL-RUN:UNKNOWN-JOB");
});

test("validateWorkflow() when on is missing", (t) => {
const errors = actionsutil.validateWorkflow({});

t.deepEqual(errors, ["Please specify on.push and on.pull_request hooks."]);
});

test("validateWorkflow() when on.push is missing", (t) => {
const errors = actionsutil.validateWorkflow({ on: {} });

console.log(errors);

t.deepEqual(errors, ["Please specify on.push and on.pull_request hooks."]);
});

test("validateWorkflow() when on.push is an array missing pull_request", (t) => {
const errors = actionsutil.validateWorkflow({ on: ["push"] });

t.deepEqual(errors, [
"Please specify an on.pull_request hook so CodeQL is run against new pull requests.",
]);
});

test("validateWorkflow() when on.push is an array missing push", (t) => {
const errors = actionsutil.validateWorkflow({ on: ["pull_request"] });

t.deepEqual(errors, [
"Please specify an on.push hook so CodeQL can establish a baseline.",
]);
});

test("validateWorkflow() when on.push is valid", (t) => {
const errors = actionsutil.validateWorkflow({
on: ["push", "pull_request"],
});

t.deepEqual(errors.length, 0);
});

test("validateWorkflow() when on.push is a valid superset", (t) => {
const errors = actionsutil.validateWorkflow({
on: ["push", "pull_request", "schedule"],
});

t.deepEqual(errors.length, 0);
});

test("validateWorkflow() when on.push should not have a path", (t) => {
const errors = actionsutil.validateWorkflow({
on: {
push: { branches: ["main"], paths: ["test/*"] },
pull_request: { branches: ["main"] },
},
});

t.deepEqual(errors, ["Please do not specify paths at on.pull."]);
});

test("validateWorkflow() when on.push is a correct object", (t) => {
const errors = actionsutil.validateWorkflow({
on: { push: { branches: ["main"] }, pull_request: { branches: ["main"] } },
});

t.deepEqual(errors.length, 0);
});

test("validateWorkflow() when on.push is correct with empty objects", (t) => {
const errors = actionsutil.validateWorkflow({
on: { push: undefined, pull_request: undefined },
});

console.log(errors);

t.deepEqual(errors.length, 0);
});

test("validateWorkflow() when on.push is mismatched", (t) => {
const errors = actionsutil.validateWorkflow({
on: {
push: { branches: ["main"] },
pull_request: { branches: ["feature"] },
},
});

t.deepEqual(errors, [
"Please make sure that any branches in on.pull_request: are also in on.push: so that CodeQL can establish a baseline.",
]);
});

test("validateWorkflow() when on.push is not mismatched", (t) => {
const errors = actionsutil.validateWorkflow({
on: {
push: { branches: ["main", "feature"] },
pull_request: { branches: ["main"] },
},
});

t.deepEqual(errors.length, 0);
});

test("validateWorkflow() when on.push is mismatched for pull_request", (t) => {
const errors = actionsutil.validateWorkflow({
on: {
push: { branches: ["main"] },
pull_request: { branches: ["main", "feature"] },
},
});

t.deepEqual(errors, [
"Please make sure that any branches in on.pull_request: are also in on.push: so that CodeQL can establish a baseline.",
]);
});

test("validateWorkflow() when HEAD^2 is checked out", (t) => {
const errors = actionsutil.validateWorkflow({
on: ["push", "pull_request"],
jobs: { test: { steps: [{ run: "git checkout HEAD^2" }] } },
});

t.deepEqual(errors, [
"Git checkout HEAD^2 is no longer necessary. Please remove this line from your workflow.",
]);
});
116 changes: 116 additions & 0 deletions src/actions-util.ts
@@ -100,6 +100,122 @@ export const getCommitOid = async function (): Promise<string> {
}
};

interface WorkflowJobStep {
run: any;
}

interface WorkflowJob {
steps?: WorkflowJobStep[];
}

interface WorkflowTrigger {
branches?: string[];
paths?: string[];
}

interface WorkflowTriggers {
push?: WorkflowTrigger;
pull_request?: WorkflowTrigger;
}

interface Workflow {
jobs?: { [key: string]: WorkflowJob };
on?: string | string[] | WorkflowTriggers;
}

function isObject(o): o is object {
return o !== null && typeof o === "object";
}

enum MissingTriggers {
NONE = 0,
PUSH = 1,
PULL_REQUEST = 2,
}

export function validateWorkflow(doc: Workflow): string[] {
const errors: string[] = [];

// .jobs[key].steps[].run
for (const job of Object.values(doc?.jobs || {})) {
for (const step of job?.steps || []) {
if (step?.run === "git checkout HEAD^2") {
errors.push(
`Git checkout HEAD^2 is no longer necessary. Please remove this line from your workflow.`
);
}
}
}

let missing = MissingTriggers.NONE;

if (doc.on === undefined) {
missing = MissingTriggers.PUSH | MissingTriggers.PULL_REQUEST;
} else if (typeof doc.on === "string") {
switch (doc.on) {
case "push":
missing = MissingTriggers.PULL_REQUEST;
break;
case "pull_request":
missing = MissingTriggers.PUSH;
break;
default:
missing = MissingTriggers.PUSH | MissingTriggers.PULL_REQUEST;
break;
}
} else if (Array.isArray(doc.on)) {
if (!doc.on.includes("push")) {
missing = missing | MissingTriggers.PUSH;
}
if (!doc.on.includes("pull_request")) {
missing = missing | MissingTriggers.PULL_REQUEST;
}
} else if (isObject(doc.on)) {
if (!Object.prototype.hasOwnProperty.call(doc.on, "pull_request")) {
missing = missing | MissingTriggers.PULL_REQUEST;
}
if (!Object.prototype.hasOwnProperty.call(doc.on, "push")) {
missing = missing | MissingTriggers.PUSH;
} else {
const paths = doc.on.push?.paths;
if (Array.isArray(paths) && paths.length > 0) {
errors.push("Please do not specify paths at on.pull.");
}
}

if (doc.on.pull_request !== undefined && doc.on.push !== undefined) {
const push = doc.on.push.branches || [];
const pull_request = doc.on.pull_request.branches || [];

const intersects = pull_request.filter((value) => !push.includes(value));

if (intersects.length > 0) {
errors.push(
"Please make sure that any branches in on.pull_request: are also in on.push: so that CodeQL can establish a baseline."
);
}
}
}

switch (missing) {
case MissingTriggers.PULL_REQUEST | MissingTriggers.PUSH:
errors.push("Please specify on.push and on.pull_request hooks.");
break;
case MissingTriggers.PULL_REQUEST:
errors.push(
"Please specify an on.pull_request hook so CodeQL is run against new pull requests."
);
break;
case MissingTriggers.PUSH:
errors.push(
"Please specify an on.push hook so CodeQL can establish a baseline."
);
break;
}

return errors;
}

/**
* Get the path of the currently executing workflow.
*/

0 comments on commit 7eb9dfc

Please sign in to comment.