-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: add secure pr checkout and labels
Signed-off-by: Ilona Shishov <[email protected]>
- Loading branch information
1 parent
401aa92
commit 7b521e8
Showing
14 changed files
with
735 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import * as ghCore from "@actions/core"; | ||
import * as github from "@actions/github"; | ||
import { Octokit } from "@octokit/core"; | ||
|
||
import * as types from './types.js' | ||
import * as labels from "./labels.js"; | ||
import { RhdaLabels } from "./labels.js"; | ||
import { getGhToken, prettifyHttpError } from "../utils.js"; | ||
|
||
export async function isPrScanApproved(pr: types.PrData): Promise<boolean> { | ||
ghCore.info(`Scan is running in a pull request, checking for approval label...`); | ||
|
||
// get author authorization | ||
let prAuthorHasWriteAccess = await canPrAuthorWrite(pr); | ||
|
||
// update labels | ||
const availableLabels = await labels.getLabels(pr.number); | ||
if (availableLabels.length !== 0) { | ||
ghCore.info(`Pull request labels are: ${availableLabels.map((s) => `"${s}"`).join(", ")}`); | ||
} | ||
else { | ||
ghCore.info("No labels found"); | ||
} | ||
|
||
const prAction = github.context.payload.action; | ||
ghCore.info(`Action performed is "${prAction}"`); | ||
|
||
if (prAction === "edited" || prAction === "synchronize") { | ||
ghCore.info(`Code change detected`); | ||
|
||
let labelsToRemove = labels.findLabelsToRemove(availableLabels); | ||
|
||
// if pr author has write access do not remove approved label | ||
if (prAuthorHasWriteAccess) { | ||
labelsToRemove = labelsToRemove.filter(label => label !== RhdaLabels.RHDA_SCAN_APPROVED); | ||
} | ||
|
||
if (labelsToRemove.length > 0) { | ||
await labels.removeLabelsFromPr(pr.number, labelsToRemove); | ||
} | ||
|
||
if (prAuthorHasWriteAccess) { | ||
return true; | ||
} | ||
ghCore.info(`Adding "${RhdaLabels.RHDA_SCAN_PENDING}" label.`); | ||
await labels.addLabelsToPr(pr.number, [ RhdaLabels.RHDA_SCAN_PENDING ]); | ||
|
||
return false; | ||
} | ||
|
||
if (availableLabels.includes(RhdaLabels.RHDA_SCAN_APPROVED)) { | ||
if (availableLabels.includes(RhdaLabels.RHDA_SCAN_PENDING)) { | ||
await labels.removeLabelsFromPr(pr.number, [ RhdaLabels.RHDA_SCAN_PENDING ]); | ||
} | ||
ghCore.info(`"${RhdaLabels.RHDA_SCAN_APPROVED}" label is present`); | ||
return true; | ||
} | ||
|
||
if (prAuthorHasWriteAccess) { | ||
await labels.addLabelsToPr(pr.number, [ RhdaLabels.RHDA_SCAN_APPROVED ]); | ||
|
||
return true; | ||
} | ||
|
||
if (!availableLabels.includes(RhdaLabels.RHDA_SCAN_PENDING)) { | ||
await labels.addLabelsToPr(pr.number, [ RhdaLabels.RHDA_SCAN_PENDING ]); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
// API documentation: https://docs.github.com/en/rest/reference/repos#get-repository-permissions-for-a-user | ||
async function canPrAuthorWrite(pr: types.PrData): Promise<boolean> { | ||
if (!pr.author) { | ||
ghCore.warning(`Failed to determine pull request author`); | ||
return false; | ||
} | ||
ghCore.info(`Pull request author is "${pr.author}"`); | ||
|
||
const octokit = new Octokit({ auth: getGhToken() }); | ||
const { owner, repo } = github.context.repo; | ||
let authorPermissionResponse; | ||
try { | ||
ghCore.info(`Checking if the user "${pr.author}" has write ` | ||
+ `access to repository "${owner}/${repo}"`); | ||
authorPermissionResponse = await octokit.request( | ||
"GET /repos/{owner}/{repo}/collaborators/{username}/permission", { | ||
owner, | ||
repo, | ||
username: pr.author, | ||
} | ||
); | ||
} | ||
catch (err) { | ||
throw prettifyHttpError(err); | ||
} | ||
|
||
const permission = authorPermissionResponse.data.permission; | ||
if (permission === "admin" || permission === "write") { | ||
ghCore.info(`User has write access to the repository`); | ||
return true; | ||
} | ||
ghCore.info(`User doesn't has write access to the repository`); | ||
|
||
return false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import * as ghCore from '@actions/core'; | ||
|
||
import { getGitExecutable, execCommand } from "../utils.js"; | ||
|
||
export async function getOriginalCheckoutBranch(): Promise<string> { | ||
const { exitCode, stdout, stderr } = await execCommand(getGitExecutable(), [ "branch", "--show-current" ]); | ||
if (exitCode != 0) { | ||
throw(new Error(stderr)); | ||
} | ||
return stdout.trim(); | ||
} | ||
|
||
/** | ||
* Checkout PR code to run the CRDA Analysis on a PR, | ||
* After completion of the scan this created remote and branch | ||
* will be deleted and branch will be checkedout the present branch | ||
*/ | ||
export async function checkoutPr(baseRepoUrl: string, prNumber: number): Promise<void> { | ||
const remoteName = getPRRemoteName(prNumber); | ||
const localbranchName = getPRBranchName(prNumber); | ||
|
||
ghCore.info(`Adding remote ${baseRepoUrl}`); | ||
await execCommand(getGitExecutable(), [ "remote", "add", remoteName, baseRepoUrl ]); | ||
|
||
ghCore.info(`⬇️ Checking out PR #${prNumber} to run RHDA analysis.`); | ||
await execCommand(getGitExecutable(), [ "fetch", remoteName, `pull/${prNumber}/head:${localbranchName}` ]); | ||
await execCommand(getGitExecutable(), [ "checkout", localbranchName ]); | ||
} | ||
|
||
// Do cleanup after the crda scan and checkout | ||
// back to the original branch | ||
export async function checkoutCleanup(prNumber: number, originalCheckoutBranch: string): Promise<void> { | ||
const remoteName = getPRRemoteName(prNumber); | ||
const branchName = getPRBranchName(prNumber); | ||
|
||
ghCore.info(`Checking out back to ${originalCheckoutBranch} branch.`); | ||
await execCommand(getGitExecutable(), [ "checkout", originalCheckoutBranch ]); | ||
|
||
ghCore.info(`Removing the created remote "${remoteName}"`); | ||
await execCommand(getGitExecutable(), [ "remote", "remove", remoteName ]); | ||
|
||
ghCore.info(`Removing created branch "${branchName}"`); | ||
await execCommand(getGitExecutable(), [ "branch", "-D", `${branchName}` ]); | ||
} | ||
|
||
function getPRRemoteName(prNumber: number): string { | ||
return `remote-${prNumber}`; | ||
} | ||
|
||
function getPRBranchName(prNumber: number): string { | ||
return `pr-${prNumber}`; | ||
} |
Oops, something went wrong.