From eae3be88c8db7cca11a5f5ae8dc4fb015b2d4bc5 Mon Sep 17 00:00:00 2001 From: Andrea Brancaleoni Date: Fri, 24 May 2024 18:53:31 +0200 Subject: [PATCH] action.cjs: debug more --- .github/workflows/full-loop.yml | 1 + action.cjs | 73 ++++++++++++++++++++++++++++----- src/steps/assigneeRemoved.js | 10 ++--- src/steps/assigneesAfter.js | 13 +++--- 4 files changed, 76 insertions(+), 21 deletions(-) diff --git a/.github/workflows/full-loop.yml b/.github/workflows/full-loop.yml index 8f602713..4cdf3f83 100644 --- a/.github/workflows/full-loop.yml +++ b/.github/workflows/full-loop.yml @@ -25,6 +25,7 @@ jobs: baseline_scan_only: false gh_to_slack_user_map: ${{ secrets.GH_TO_SLACK_USER_MAP }} - run: | + set -e echo ${{ steps.action.outputs.reviewdog-findings }} if ((${{ steps.action.outputs.reviewdog-findings }} < 106)); then echo "Too few reviewdog findings" diff --git a/action.cjs b/action.cjs index 62b785ad..aa6e61b7 100644 --- a/action.cjs +++ b/action.cjs @@ -4,20 +4,53 @@ function hashFiles (filename) { return fs.existsSync(filename) } +const { spawn } = require('child_process') + +function runCommand () { + const args = Array.prototype.slice.call(arguments) + return new Promise((resolve, reject) => { + const childProcess = spawn.apply(null, args) + + childProcess.stdout.on('data', (data) => { + console.log(`stdout: ${data}`) + }) + + childProcess.stderr.on('data', (data) => { + console.error(`stderr: ${data}`) + }) + + childProcess.on('close', (code) => { + if (code !== 0) { + reject(new Error(`Command exited with code ${code}`)) + } else { + resolve() + } + }) + }) +} + module.exports = async ({ github, context, inputs, actionPath, core }) => { - if (inputs.enabled === 'true') { return } + // const debug = inputs.debug === 'true' ? console.log : () => {} + const debug = console.log + + if (inputs.enabled !== 'true') { return } + debug('Security Action enabled') // reviewdog-enabled-pr steps const reviewdogEnabledPr = inputs.baseline_scan_only !== 'false' && process.env.GITHUB_EVENT_NAME === 'pull_request' && context.payload.pull_request.draft === false && context.actor !== 'dependabot[bot]' + debug(`Security Action enabled for PR: ${reviewdogEnabledPr}, baseline_scan_only: ${inputs.baseline_scan_only}, GITHUB_EVENT_NAME: ${process.env.GITHUB_EVENT_NAME}, context.actor: ${context.actor}, context.payload.pull_request.draft: ${context.payload.pull_request.draft}`) // reviewdog-enabled-full steps const reviewdogEnabledFull = !reviewdogEnabledPr && (inputs.baseline_scan_only === 'false' || process.env.GITHUB_EVENT_NAME === 'workflow_dispatch') + debug(`Security Action enabled for full: ${reviewdogEnabledFull}, baseline_scan_only: ${inputs.baseline_scan_only}, GITHUB_EVENT_NAME: ${process.env.GITHUB_EVENT_NAME}`) // reviewdog-enabled steps if (!reviewdogEnabledPr && !reviewdogEnabledFull) { return } + debug('Security Action enabled for reviewdog') - const { execSync } = require('child_process') // Install semgrep & pip-audit - execSync(`pip install --disable-pip-version-check -r ${actionPath}/requirements.txt`) + await runCommand(`pip install --disable-pip-version-check -r ${actionPath}/requirements.txt`, { shell: true }) + debug('Installed semgrep & pip-audit') // Install xmllint for safesvg - execSync('sudo apt-get install -y libxml2-utils') + await runCommand('sudo apt-get install -y libxml2-utils', { shell: true }) + debug('Installed xmllint') // debug step if (inputs.debug === 'true') { @@ -25,28 +58,33 @@ module.exports = async ({ github, context, inputs, actionPath, core }) => { ...process.env, ASSIGNEES: inputs.assignees } - execSync(`${actionPath}/assets/debug.sh`, { env }) + await runCommand(`${actionPath}/assets/debug.sh`, { env }) + debug('Debug step completed') } // run-reviewdog-full step if (reviewdogEnabledFull) { const env = { ...process.env } delete env.GITHUB_BASE_REF - execSync(`${actionPath}/assets/reviewdog.sh`, { env }) + await runCommand(`${actionPath}/assets/reviewdog.sh`, { env }) + debug('Reviewdog full step completed') } if (reviewdogEnabledPr) { // changed-files steps const { default: pullRequestChangedFiles } = await import(`${actionPath}/src/pullRequestChangedFiles.js`) - const changedFiles = pullRequestChangedFiles({ github, owner: context.repo.owner, name: context.repo.repo, prnumber: context.payload.pull_request.number }) + const changedFiles = await pullRequestChangedFiles({ github, owner: context.repo.owner, name: context.repo.repo, prnumber: context.payload.pull_request.number }) + debug('Changed files:', changedFiles) // Write changed files to file const fs = require('fs') fs.writeFileSync(`${actionPath}/assets/all_changed_files.txt`, changedFiles.join('\0')) + debug('Wrote changed files to file') // comments-before steps const { default: commentsNumber } = await import(`${actionPath}/src/steps/commentsNumber.js`) const { default: cleanupComments } = await import(`${actionPath}/src/steps/cleanupComments.js`) + debug('Comments before:', await commentsNumber({ context, github })) const commentsBefore = await commentsNumber({ context, github }) await cleanupComments({ context, github }) @@ -63,6 +101,7 @@ module.exports = async ({ github, context, inputs, actionPath, core }) => { issue_number: context.issue.number, labels: ['unverified-commits'] }) + debug('Added unverified-commits label') } // run-reviewdog-pr step @@ -74,25 +113,31 @@ module.exports = async ({ github, context, inputs, actionPath, core }) => { PYPI_INDEX_URL: inputs.pip_audit_pypi_index_url, PYPI_INSECURE_HOSTS: inputs.pip_audit_pypi_insecure_hosts } - execSync(`${github.action_path}/assets/reviewdog.sh`, { env }) + await runCommand(`${actionPath}/assets/reviewdog.sh`, { env }) + debug('Reviewdog PR step completed') // comments-after step const commentsAfter = await commentsNumber({ context, github }) + debug('Comments after:', commentsAfter) // assignees-after step const { default: assigneesAfter } = await import(`${actionPath}/src/steps/assigneesAfter.js`) const assigneesAfterVal = await assigneesAfter({ context, github, assignees: inputs.assignees }) + debug('Assignees after:', assigneesAfterVal) // assignee-removed-label step const { default: assigneeRemoved } = await import(`${actionPath}/src/steps/assigneeRemoved.js`) const assigneeRemovedLabel = await assigneeRemoved({ context, github, assignees: assigneesAfterVal }) + debug('Assignee removed:', assigneeRemovedLabel) // add description-contains-hotwords step const { default: hotwords } = await import(`${actionPath}/src/steps/hotwords.js`) const descriptionContainsHotwords = (context.actor !== 'renovate[bot]') ? await hotwords({ context, github, hotwords: inputs.hotwords }) : false + debug('Description contains hotwords:', descriptionContainsHotwords) // add should-trigger label step const shouldTrigger = reviewdogEnabledPr && !assigneeRemovedLabel && ((commentsBefore !== commentsAfter) || descriptionContainsHotwords) + debug('Should trigger:', shouldTrigger) if (shouldTrigger) { // add label step @@ -102,16 +147,18 @@ module.exports = async ({ github, context, inputs, actionPath, core }) => { issue_number: context.issue.number, labels: ['needs-security-review'] }) + debug('Added needs-security-review label') // add assignees step await github.rest.issues.addAssignees({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - assigneesAfterVal + assignees: assigneesAfterVal.split(/\s+/).filter((str) => str !== '') }) + debug('Added assignees') } - const { default: sendSlackMessage } = await import(`${actionPath}/src/steps/sendSlackMessage.js`) + const { default: sendSlackMessage } = await import(`${actionPath}/src/sendSlackMessage.js`) const message = `Repository: ${process.env.GITHUB_REPOSITORY}\npull-request: ${context.payload.pull_request.html_url}` @@ -126,6 +173,7 @@ module.exports = async ({ github, context, inputs, actionPath, core }) => { const assignees = assigneesAfterVal.toLowerCase().split(/\s+/).map(e => e.trim()).filter(Boolean) const slackAssignees = assignees.map(m => githubToSlack[m] ? githubToSlack[m] : `@${m}`).join(' ') core.setSecret(slackAssignees) + debug('Slack assignees:', slackAssignees) // actor-slack step const actor = githubToSlack[context.actor] ? githubToSlack[context.actor] : `@${context.actor}` @@ -138,10 +186,12 @@ module.exports = async ({ github, context, inputs, actionPath, core }) => { console.log('\x1b[0;31mThis action encountered an error while reporting the following findings via the Github API:') console.log(log) console.log('\x1b[0;31mThe failure of this action should not prevent you from merging your PR. Please report this failure to the maintainers of https://github.com/brave/security-action \x1b[0m') + debug('Error log printed to console') if (inputs.slack_token) { // reviewdog-fail-log-head step const reviewdogFailLogHead = '\n' + require('fs').readFileSync('reviewdog.fail.log', 'UTF-8').split('\n').slice(0, 4).join('\n') + debug('Reviewdog fail log head:', reviewdogFailLogHead) // send error slack message, if there is any error await sendSlackMessage({ @@ -151,8 +201,10 @@ module.exports = async ({ github, context, inputs, actionPath, core }) => { channel: '#secops-hotspots', color: 'red' }) + debug('Sent error slack message') } else { // throw error if no slack token is provided, and there is an error log + debug('Error was thrown and Slack token is missing, exiting eagerly!') throw new Error('Error was thrown and Slack token is missing, exiting eagerly!') } } @@ -166,6 +218,7 @@ module.exports = async ({ github, context, inputs, actionPath, core }) => { channel: '#secops-hotspots', color: 'green' }) + debug('Comments after:', commentsAfter) } } } diff --git a/src/steps/assigneeRemoved.js b/src/steps/assigneeRemoved.js index a3da6d69..144ff3cf 100644 --- a/src/steps/assigneeRemoved.js +++ b/src/steps/assigneeRemoved.js @@ -1,11 +1,11 @@ export default async function assigneeRemoved ({ context, github, - githubToken + githubToken, + assignees }) { - const { ASSIGNEES } = process.env - console.log('assignees: %o', ASSIGNEES) - const assignees = ASSIGNEES.split(/\s+/).filter((str) => str !== '') + console.log('assignees: %o', assignees) + const assigneesOutput = assignees.split(/\s+/).filter((str) => str !== '') const query = `query ($owner: String!, $name: String!, $prnumber: Int!) { repository(owner: $owner, name: $name) { pullRequest(number: $prnumber) { @@ -35,7 +35,7 @@ export default async function assigneeRemoved ({ const removedByAssigneeEvents = timelineItems.nodes.filter( timelineItem => ( timelineItem.label.name === 'needs-security-review' && - assignees.some((a) => timelineItem.actor.login === a) + assigneesOutput.some((a) => timelineItem.actor.login === a) ) ).length console.log('RemovedByAssigneeEvents: %d', removedByAssigneeEvents) diff --git a/src/steps/assigneesAfter.js b/src/steps/assigneesAfter.js index 5394d204..ed8d1114 100644 --- a/src/steps/assigneesAfter.js +++ b/src/steps/assigneesAfter.js @@ -1,6 +1,7 @@ export default async function assigneesAfter ({ github, - context + context, + assignees }) { const query = `query($owner:String!, $name:String!, $prnumber:Int!) { repository(owner:$owner, name:$name) { @@ -29,7 +30,7 @@ export default async function assigneesAfter ({ } const result = await github.graphql(query, variables) const threads = result.repository.pullRequest.reviewThreads - const assignees = [...new Set(threads.nodes.filter( + const outputAssignees = [...new Set(threads.nodes.filter( reviewThread => ( reviewThread.comments.nodes[0].author.login === 'github-actions' && reviewThread.comments.nodes[0].body.includes('
Cc ') @@ -40,10 +41,10 @@ export default async function assigneesAfter ({ .replaceAll('@', '').trim().split(' ') ).flat())] - console.log('assignees: %o', assignees) - if (assignees.length > 0) { - return assignees.join('\n') + console.log('assignees: %o', outputAssignees) + if (outputAssignees.length > 0) { + return outputAssignees.join('\n') } else { - return process.env.ASSIGNEES.split(/\s+/).filter((str) => str !== '').join('\n') + return assignees.split(/\s+/).filter((str) => str !== '').join('\n') } }