Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add checks mode input #241

Merged
merged 3 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 118 additions & 2 deletions __tests__/functions/prechecks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ beforeEach(() => {
allowForks: true,
skipCi: '',
skipReviews: '',
draft_permitted_targets: ''
draft_permitted_targets: '',
checks: 'all'
}
}

Expand Down Expand Up @@ -87,7 +88,23 @@ beforeEach(() => {
totalCount: 3
},
statusCheckRollup: {
state: 'SUCCESS'
state: 'SUCCESS',
contexts: {
nodes: [
{
isRequired: true,
conclusion: 'SUCCESS'
},
{
isRequired: true,
conclusion: 'SKIPPED'
},
{
isRequired: false,
conclusion: 'SUCCESS'
}
]
}
}
}
}
Expand Down Expand Up @@ -133,6 +150,57 @@ test('runs prechecks and finds that the IssueOps command is valid for a branch d
})
})

test('runs prechecks and finds that the IssueOps command is valid for a branch deployment with required checks', async () => {
octokit.graphql = jest.fn().mockReturnValue({
repository: {
pullRequest: {
reviewDecision: 'APPROVED',
mergeStateStatus: 'CLEAN',
commits: {
nodes: [
{
commit: {
checkSuites: {
totalCount: 3
},
statusCheckRollup: {
state: 'FAILURE',
contexts: {
nodes: [
{
isRequired: true,
conclusion: 'SUCCESS'
},
{
isRequired: true,
conclusion: 'SKIPPED'
},
{
isRequired: false,
conclusion: 'FAILURE'
}
]
}
}
}
}
]
}
}
}
})

data.inputs.checks = 'required'

expect(await prechecks(context, octokit, data)).toStrictEqual({
message: '✅ PR is approved and all CI checks passed',
noopMode: false,
ref: 'test-ref',
status: true,
sha: 'abc123'
})
})

test('runs prechecks and finds that the IssueOps command is valid for a rollback deployment', async () => {
octokit.rest.repos.getBranch = jest
.fn()
Expand Down Expand Up @@ -619,6 +687,54 @@ test('runs prechecks and finds the PR is approved but CI is failing', async () =
})
})

test('runs prechecks and finds the PR is approved but CI is failing', async () => {
octokit.graphql = jest.fn().mockReturnValue({
repository: {
pullRequest: {
reviewDecision: 'APPROVED',
commits: {
nodes: [
{
commit: {
checkSuites: {
totalCount: 3
},
statusCheckRollup: {
state: 'FAILURE',
contexts: {
nodes: [
{
isRequired: true,
conclusion: 'SUCCESS'
},
{
isRequired: true,
conclusion: 'FAILURE'
},
{
isRequired: false,
conclusion: 'SUCCESS'
}
]
}
}
}
}
]
}
}
}
})

data.inputs.checks = 'required'

expect(await prechecks(context, octokit, data)).toStrictEqual({
message:
'### ⚠️ Cannot proceed with deployment\n\n- reviewDecision: `APPROVED`\n- commitStatus: `FAILURE`\n\n> Your pull request is approved but CI checks are failing',
status: false
})
})

test('runs prechecks and finds the PR does not require approval but CI is failing', async () => {
octokit.graphql = jest.fn().mockReturnValue({
repository: {
Expand Down
10 changes: 10 additions & 0 deletions __tests__/schemas/action.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,16 @@ inputs:
default:
type: string
required: false
checks:
description:
type: string
required: true
required:
type: boolean
required: true
default:
type: string
required: true
skip_reviews:
description:
type: string
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ inputs:
description: 'A comma separated list of environments that will not use passing CI as a requirement for deployment. Use this option to explicitly bypass branch protection settings for a certain environment in your repository. Default is an empty string "" - Example: "development,staging"'
required: false
default: ""
checks:
description: 'The mode to use for CI checks. "all" requires all checks to pass no matter what, "required" only waits for required checks.'
required: false
default: "all"
skip_reviews:
description: 'A comma separated list of environment that will not use reviews/approvals as a requirement for deployment. Use this options to explicitly bypass branch protection settings for a certain environment in your repository. Default is an empty string "" - Example: "development,staging"'
required: false
Expand Down
24 changes: 24 additions & 0 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.

21 changes: 21 additions & 0 deletions src/functions/prechecks.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function prechecks(context, octokit, data) {
const skipCi = skipCiArray.includes(data.environment)
const skipReviews = skipReviewsArray.includes(data.environment)
const allowDraftDeploy = draftPermittedTargetsArray.includes(data.environment)
const checks = data.inputs.checks

var ref = pr.data.head.ref
var noopMode = data.environmentObj.noop
Expand Down Expand Up @@ -118,6 +119,14 @@ export async function prechecks(context, octokit, data) {
}
statusCheckRollup {
state
contexts(first:100) {
nodes {
... on CheckRun {
isRequired(pullRequestNumber:$number)
conclusion
}
}
}
}
}
}
Expand Down Expand Up @@ -183,6 +192,18 @@ export async function prechecks(context, octokit, data) {
core.info('💡 no CI checks have been defined for this pull request')
commitStatus = null

// If only the required checks need to pass
} else if (checks === 'required') {
commitStatus =
result.repository.pullRequest.commits.nodes[0].commit.statusCheckRollup.contexts.nodes
.filter(x => x.isRequired)
.reduce(
(acc, x) => acc && ['SUCCESS', 'SKIPPED'].includes(x.conclusion),
true
)
? 'SUCCESS'
: 'FAILURE'

// If there are CI checked defined, we need to check for the 'state' of the latest commit
} else {
commitStatus =
Expand Down
3 changes: 3 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export async function run() {
const required_contexts = core.getInput('required_contexts')
const allowForks = core.getBooleanInput('allow_forks')
const skipCi = core.getInput('skip_ci')
const checks = core.getInput('checks')
const skipReviews = core.getInput('skip_reviews')
const mergeDeployMode = core.getBooleanInput('merge_deploy_mode')
const unlockOnMergeMode = core.getBooleanInput('unlock_on_merge_mode')
Expand Down Expand Up @@ -202,6 +203,7 @@ export async function run() {
required_contexts: required_contexts,
allowForks: allowForks,
skipCi: skipCi,
checks: checks,
skipReviews: skipReviews,
draft_permitted_targets,
admins: admins,
Expand Down Expand Up @@ -454,6 +456,7 @@ export async function run() {
issue_number: issue_number,
allowForks: allowForks,
skipCi: skipCi,
checks: checks,
skipReviews: skipReviews,
draft_permitted_targets: draft_permitted_targets
}
Expand Down