Skip to content

Commit 9e9574e

Browse files
fix #116: support composite actions (#239)
* fix #116: support composite actions * improve parsing error * trigger workflow * Revert "trigger workflow" This reverts commit cdda5ad. * specify repository name to handle validation on fork * Update index.js.map * Update index.js.map --------- Co-authored-by: Zennon Gosalvez <[email protected]>
1 parent 574bec8 commit 9e9574e

File tree

16 files changed

+291
-46
lines changed

16 files changed

+291
-46
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
2525
with:
2626
ref: ${{ github.head_ref }} # https://github.com/stefanzweifel/git-auto-commit-action#checkout-the-correct-branch
27+
repository: ${{ github.event.pull_request.head.repo.full_name }}
2728
- name: Get runs.using version
2829
id: get-runs-using-version
2930
uses: zgosalvez/github-actions-get-action-runs-using-version@0a8dcac2c28bdcbfcbdca07d46bae8eab7930dc2

dist/index.js

Lines changed: 78 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.js

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,93 @@ async function run() {
1111
try {
1212
const allowlist = core.getInput('allowlist');
1313
const isDryRun = core.getInput('dry_run') === 'true';
14+
let hasError = false;
15+
1416
const workflowsPath = process.env['ZG_WORKFLOWS_PATH'] || '.github/workflows';
15-
const globber = await glob.create([workflowsPath + '/*.yaml', workflowsPath + '/*.yml'].join('\n'));
16-
let actionHasError = false;
17+
const workflowsGlobber = await glob.create([
18+
workflowsPath + '/*.yaml',
19+
workflowsPath + '/*.yml'
20+
].join('\n'));
1721

18-
for await (const file of globber.globGenerator()) {
22+
for await (const file of workflowsGlobber.globGenerator()) {
1923
const basename = path.basename(file);
2024
const fileContents = fs.readFileSync(file, 'utf8');
2125
const yamlContents = yaml.parse(fileContents);
22-
const jobs = yamlContents['jobs'];
2326
let fileHasError = false;
2427

25-
if (jobs === undefined) {
28+
let jobs = getYamlAttribute(yamlContents, 'jobs');
29+
if (jobs === undefined || jobs === null) {
2630
core.setFailed(`The "${basename}" workflow does not contain jobs.`);
31+
break;
2732
}
2833

2934
core.startGroup(workflowsPath + '/' + basename);
3035

3136
for (const job in jobs) {
32-
const uses = jobs[job]['uses'];
33-
const steps = jobs[job]['steps'];
37+
jobObject = jobs[job];
3438
let jobHasError = false;
35-
36-
if (uses !== undefined) {
37-
jobHasError = runAssertions(uses, allowlist, isDryRun);
38-
} else if (steps !== undefined) {
39-
for (const step of steps) {
40-
if (!jobHasError) {
41-
jobHasError = runAssertions(step['uses'], allowlist, isDryRun);
39+
if (jobObject === undefined || jobObject === null) {
40+
core.warning(`The "${job}" job of the "${basename}" workflow is undefined.`);
41+
jobHasError = true;
42+
} else {
43+
const uses = getYamlAttribute(jobObject, "uses");
44+
const steps = getYamlAttribute(jobObject, "steps");
45+
if (uses !== undefined && uses !== null) {
46+
jobHasError = runAssertions(uses, allowlist, isDryRun);
47+
} else if (steps !== undefined && steps !== null) {
48+
for (const step of steps) {
49+
if (!jobHasError) {
50+
jobHasError = runAssertions(step['uses'], allowlist, isDryRun);
51+
}
4252
}
53+
} else {
54+
core.warning(`The "${job}" job of the "${basename}" workflow does not contain uses or steps.`);
4355
}
44-
} else {
45-
core.warning(`The "${job}" job of the "${basename}" workflow does not contain uses or steps.`);
4656
}
4757

4858
if (jobHasError) {
49-
actionHasError = true;
59+
hasError = true;
60+
fileHasError = true;
61+
}
62+
}
63+
64+
if (!fileHasError) {
65+
core.info('No issues were found.');
66+
}
67+
68+
core.endGroup();
69+
}
70+
71+
const actionsPath = process.env['ZG_ACTIONS_PATH'] || '.github/actions';
72+
const actionsGlobber = await glob.create([
73+
actionsPath + '/*/action.yaml',
74+
actionsPath + '/*/action.yml'
75+
].join('\n'));
76+
77+
for await (const file of actionsGlobber.globGenerator()) {
78+
const basename = path.basename(path.dirname(file));
79+
const fileContents = fs.readFileSync(file, 'utf8');
80+
const yamlContents = yaml.parse(fileContents);
81+
let fileHasError = false;
82+
83+
let runs = getYamlAttribute(yamlContents, 'runs');
84+
if (runs === undefined || runs === null) {
85+
core.setFailed(`The "${basename}" action does not contain runs.`);
86+
break;
87+
}
88+
89+
core.startGroup(actionsPath + '/' + basename);
90+
91+
let runHasError = false;
92+
const steps = getYamlAttribute(runs, 'steps');
93+
if (steps !== undefined && steps !== null) {
94+
for (const step of steps) {
95+
if (!runHasError) {
96+
runHasError = runAssertions(step['uses'], allowlist, isDryRun);
97+
}
98+
}
99+
if (runHasError) {
100+
hasError = true;
50101
fileHasError = true;
51102
}
52103
}
@@ -58,8 +109,8 @@ async function run() {
58109
core.endGroup();
59110
}
60111

61-
if (!isDryRun && actionHasError) {
62-
throw new Error('At least one workflow contains an unpinned GitHub Action version.');
112+
if (!isDryRun && hasError) {
113+
throw new Error('At least one workflow or composite action contains an unpinned GitHub Action version.');
63114
}
64115
} catch (error) {
65116
core.setFailed(error.message);
@@ -68,6 +119,13 @@ async function run() {
68119

69120
run();
70121

122+
function getYamlAttribute(yamlContents, attribute) {
123+
if (yamlContents && typeof yamlContents === 'object' && Object.prototype.hasOwnProperty.call(yamlContents, attribute)) {
124+
return yamlContents[attribute];
125+
}
126+
return undefined;
127+
}
128+
71129
function assertUsesVersion(uses) {
72130
return typeof uses === 'string' && uses.includes('@');
73131
}
@@ -89,7 +147,7 @@ function assertUsesAllowlist(uses, allowlist) {
89147
const isAllowed = allowlist.split(/\r?\n/).some((allow) => action.startsWith(allow));
90148

91149
if(isAllowed) {
92-
core.info(`${action} matched allowlist — ignoring action.`)
150+
core.info(`${action} matched allowlist — ignoring action.`);
93151
}
94152

95153
return isAllowed;

0 commit comments

Comments
 (0)