Skip to content

Commit

Permalink
Merge pull request #345 from github/branch-ruleset-fixes
Browse files Browse the repository at this point in the history
bug: Branch Ruleset API call fails when a user/org doesn't have the ruleset feature
  • Loading branch information
GrantBirki authored Dec 16, 2024
2 parents 242b260 + 8971daa commit 1c7b2cc
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {branchRulesetChecks} from '../../src/functions/branch-ruleset-checks'
import * as core from '@actions/core'
import {COLORS} from '../../src/functions/colors'
import {SUGGESTED_RULESETS} from '../../src/functions/suggested-rulesets'
import {ERROR} from '../../src/functions/templates/error'

var context
var octokit
Expand All @@ -12,6 +13,13 @@ const debugMock = jest.spyOn(core, 'debug').mockImplementation(() => {})
const warningMock = jest.spyOn(core, 'warning').mockImplementation(() => {})
const infoMock = jest.spyOn(core, 'info').mockImplementation(() => {})

class ForbiddenError extends Error {
constructor(message) {
super(message)
this.status = 403
}
}

beforeEach(() => {
jest.spyOn(core, 'info').mockImplementation(() => {})
jest.spyOn(core, 'debug').mockImplementation(() => {})
Expand Down Expand Up @@ -258,3 +266,41 @@ test('should still pass even with many required reviewers', async () => {
`required_approving_review_count is 4 - OK`
)
})

test('fails due to a 403 from the GitHub API due to a repository being private on the free tier without access to repo rulesets', async () => {
octokit = {
rest: {
repos: {
getBranchRules: jest
.fn()
.mockRejectedValueOnce(
new ForbiddenError(ERROR.messages.upgrade_or_public.message)
)
}
}
}
expect(await branchRulesetChecks(context, octokit, data)).toStrictEqual({
success: false,
failed_checks: ['upgrade_or_public_required']
})
expect(debugMock).toHaveBeenCalledWith(
ERROR.messages.upgrade_or_public.help_text
)
})

test('fails due to an unknown 403 from the GitHub API', async () => {
const errorMessage = 'oh no, something went wrong - forbidden'
octokit = {
rest: {
repos: {
getBranchRules: jest
.fn()
.mockRejectedValueOnce(new ForbiddenError(errorMessage))
}
}
}

await expect(branchRulesetChecks(context, octokit, data)).rejects.toThrow(
errorMessage
)
})
110 changes: 69 additions & 41 deletions dist/index.js

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

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

97 changes: 56 additions & 41 deletions src/functions/branch-ruleset-checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as core from '@actions/core'
import {COLORS} from './colors'
import {API_HEADERS} from './api-headers'
import {SUGGESTED_RULESETS} from './suggested-rulesets'
import {ERROR} from './templates/error'

export async function branchRulesetChecks(context, octokit, data) {
const branch = data.branch
Expand All @@ -12,53 +13,67 @@ export async function branchRulesetChecks(context, octokit, data) {
return {success: true}
}

const {data: branchRules} = await octokit.rest.repos.getBranchRules({
...context.repo,
branch,
headers: API_HEADERS
})
try {
const {data: branchRules} = await octokit.rest.repos.getBranchRules({
...context.repo,
branch,
headers: API_HEADERS
})

core.debug(
`branch ${COLORS.highlight}rulesets${COLORS.reset}: ${JSON.stringify(branchRules)}`
)
core.debug(
`branch ${COLORS.highlight}rulesets${COLORS.reset}: ${JSON.stringify(branchRules)}`
)

const failed_checks = []
const failed_checks = []

// Leave a warning if no rulesets are defined
if (branchRules.length === 0) {
core.warning(
`🔐 branch ${COLORS.highlight}rulesets${COLORS.reset} are not defined for branch ${COLORS.highlight}${branch}${COLORS.reset}`
)
failed_checks.push('missing_branch_rulesets')
} else {
// Loop through the suggested rulesets and check them against the branch rules
SUGGESTED_RULESETS.forEach(suggestedRule => {
const {type: ruleType, parameters: ruleParameters} = suggestedRule

const branchRule = branchRules.find(rule => rule.type === ruleType)

if (!branchRule) {
logMissingRule(branch, ruleType, failed_checks)
} else if (ruleParameters) {
checkRuleParameters(
branch,
ruleType,
ruleParameters,
branchRule,
failed_checks
)
}
})
}

// Leave a warning if no rulesets are defined
if (branchRules.length === 0) {
core.warning(
`🔐 branch ${COLORS.highlight}rulesets${COLORS.reset} are not defined for branch ${COLORS.highlight}${branch}${COLORS.reset}`
)
failed_checks.push('missing_branch_rulesets')
} else {
// Loop through the suggested rulesets and check them against the branch rules
SUGGESTED_RULESETS.forEach(suggestedRule => {
const {type: ruleType, parameters: ruleParameters} = suggestedRule

const branchRule = branchRules.find(rule => rule.type === ruleType)

if (!branchRule) {
logMissingRule(branch, ruleType, failed_checks)
} else if (ruleParameters) {
checkRuleParameters(
branch,
ruleType,
ruleParameters,
branchRule,
failed_checks
)
}
})
}
logWarnings(failed_checks)

logWarnings(failed_checks)
// If there are no failed checks, log a success message
if (failed_checks.length === 0) {
core.info(
`🔐 branch ruleset checks ${COLORS.success}passed${COLORS.reset}`
)
}

// If there are no failed checks, log a success message
if (failed_checks.length === 0) {
core.info(`🔐 branch ruleset checks ${COLORS.success}passed${COLORS.reset}`)
return {success: failed_checks.length === 0, failed_checks}
} catch (error) {
if (
error.status === ERROR.messages.upgrade_or_public.status &&
error.message.includes(ERROR.messages.upgrade_or_public.message)
) {
core.debug(ERROR.messages.upgrade_or_public.help_text)
return {success: false, failed_checks: ['upgrade_or_public_required']}
} else {
throw error
}
}

return {success: failed_checks.length === 0, failed_checks}
}

function logMissingRule(branch, ruleType, failed_checks) {
Expand Down
11 changes: 11 additions & 0 deletions src/functions/templates/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const ERROR = {
messages: {
upgrade_or_public: {
status: 403,
message:
'Upgrade to GitHub Pro or make this repository public to enable this feature',
help_text:
'Rulesets are available in public repositories with GitHub Free and GitHub Free for organizations, and in public and private repositories with GitHub Pro, GitHub Team, and GitHub Enterprise Cloud. For more information see https://docs.github.com/en/get-started/learning-about-github/githubs-plans or https://docs.github.com/rest/repos/rules#get-rules-for-a-branch - (Upgrade to GitHub Pro or make this repository public to enable this feature.)'
}
}
}

0 comments on commit 1c7b2cc

Please sign in to comment.