Skip to content

Commit

Permalink
Merge pull request #361 from github/feat/allow_non_default_target_bra…
Browse files Browse the repository at this point in the history
…nch_deployments

feat: allow non-default target branch deployments via a new input option
  • Loading branch information
GrantBirki authored Jan 22, 2025
2 parents 8ee0839 + a9d963b commit c8417fc
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 20 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ As seen above, we have two steps. One for a noop deploy, and one for a regular d
| `skip_successful_deploy_labels_if_approved` | `false` | `"false"` | Whether or not the post run logic should skip adding successful deploy labels if the pull request is approved. This can be useful if you add a label such as "ready-for-review" after a `.deploy` completes but want to skip adding that label in situations where the pull request is already approved. |
| `enforced_deployment_order` | `false` | `""` | A comma separated list of environments that must be deployed in a specific order. Example: `"development,staging,production"`. If this is set then you cannot deploy to latter environments unless the former ones have a successful and active deployment on the latest commit first - See the [enforced deployment order docs](./docs/enforced-deployment-order.md) for more details |
| `use_security_warnings` | `false` | `"true"` | Whether or not to leave security related warnings in log messages during deployments. Default is `"true"` |
| `allow_non_default_target_branch_deployments` | `false` | `"false"` | Whether or not to allow deployments of pull requests that target a branch other than the default branch (aka stable branch) as their merge target. By default, this Action would reject the deployment of a branch named `feature-branch` if it was targeting `foo` instead of `main` (or whatever your default branch is). This option allows you to override that behavior and be able to deploy any branch in your repository regardless of the target branch. This option is potentially unsafe and should be used with caution as most default branches contain branch protection rules. Often times non-default branches do not contain these same branch protection rules. Follow along in this [issue thread](https://github.com/github/branch-deploy/issues/340) to learn more. |

## Outputs 📤

Expand Down Expand Up @@ -342,6 +343,7 @@ As seen above, we have two steps. One for a noop deploy, and one for a regular d
| `needs_to_be_deployed` | A comma separated list of environments that need successful and active deployments before the current environment (that was requested) can be deployed. This output is tied to the `enforced_deployment_order` input option - See the [enforced deployment order docs](./docs/enforced-deployment-order.md) for more details |
| `commit_verified` | The string `"true"` if the commit is verified, otherwise `"false"` |
| `total_seconds` | The total number of seconds that the deployment took to complete (Integer) |
| `non_default_target_branch_used` | The string `"true"` if the pull request is targeting a branch other than the default branch (aka stable branch) for the merge, otherwise unset |

## Custom Deployment Messages ✏️

Expand Down
17 changes: 13 additions & 4 deletions __tests__/functions/help.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ const defaultInputs = {
commit_verification: true,
ignored_checks: [],
enforced_deployment_order: [],
use_security_warnings: true
use_security_warnings: true,
allow_non_default_target_branch_deployments: false
}

test('successfully calls help with defaults', async () => {
Expand Down Expand Up @@ -89,7 +90,8 @@ test('successfully calls help with non-defaults', async () => {
ignored_checks: ['lint', 'format'],
commit_verification: false,
enforced_deployment_order: [],
use_security_warnings: false
use_security_warnings: false,
allow_non_default_target_branch_deployments: false
}

expect(await help(octokit, context, 123, inputs))
Expand Down Expand Up @@ -127,7 +129,8 @@ test('successfully calls help with non-defaults again', async () => {
ignored_checks: ['lint'],
commit_verification: false,
enforced_deployment_order: ['development', 'staging', 'production'],
use_security_warnings: false
use_security_warnings: false,
allow_non_default_target_branch_deployments: false
}

expect(await help(octokit, context, 123, inputs))
Expand Down Expand Up @@ -176,7 +179,8 @@ test('successfully calls help with non-defaults and unknown update_branch settin
checks: 'required',
ignored_checks: ['lint'],
enforced_deployment_order: [],
use_security_warnings: false
use_security_warnings: false,
allow_non_default_target_branch_deployments: true
}

expect(await help(octokit, context, 123, inputs))
Expand All @@ -197,4 +201,9 @@ test('successfully calls help with non-defaults and unknown update_branch settin
expect(debugMock).toHaveBeenCalledWith(
expect.stringMatching(/not use security warnings/)
)
expect(debugMock).toHaveBeenCalledWith(
expect.stringMatching(
/will allow the deployments of pull requests that target a branch other than the default branch/
)
)
})
190 changes: 189 additions & 1 deletion __tests__/functions/prechecks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ beforeEach(() => {
checks: 'all',
permissions: ['admin', 'write'],
commit_verification: false,
ignored_checks: []
ignored_checks: [],
use_security_warnings: true,
allow_non_default_target_branch_deployments: false
}
}

Expand Down Expand Up @@ -1033,6 +1035,11 @@ test('runs prechecks and finds that the IssueOps command is valid for a branch d
sha: 'abcde12345',
isFork: true
})

expect(setOutputMock).not.toHaveBeenCalledWith(
'non_default_target_branch_used',
'true'
)
})

test('runs prechecks and finds that the PR from a fork is targeting a non-default branch and rejects the deployment', async () => {
Expand Down Expand Up @@ -1081,6 +1088,187 @@ test('runs prechecks and finds that the PR from a fork is targeting a non-defaul
message: `### ⚠️ Cannot proceed with deployment\n\nThis pull request is attempting to merge into the \`some-other-branch\` branch which is not the default branch of this repository (\`${data.inputs.stable_branch}\`). This deployment has been rejected since it could be dangerous to proceed.`,
status: false
})

expect(setOutputMock).toHaveBeenCalledWith(
'non_default_target_branch_used',
'true'
)
})

test('runs prechecks and finds that the PR from a fork is targeting a non-default branch and allows it based on the action config', async () => {
octokit.graphql = jest.fn().mockReturnValue({
repository: {
pullRequest: {
reviewDecision: 'APPROVED',
reviews: {
totalCount: 1
},
commits: {
nodes: [
{
commit: {
oid: 'abcde12345',
checkSuites: {
totalCount: 8
},
statusCheckRollup: {
state: 'SUCCESS'
}
}
}
]
}
}
}
})
octokit.rest.pulls.get = jest.fn().mockReturnValue({
data: {
head: {
sha: 'abcde12345',
ref: 'test-ref',
label: 'test-repo:test-ref',
repo: {
fork: true
}
},
base: {
ref: 'some-other-branch'
}
},
status: 200
})

data.inputs.allow_non_default_target_branch_deployments = true

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

expect(setOutputMock).toHaveBeenCalledWith(
'non_default_target_branch_used',
'true'
)
})

test('runs prechecks and finds that the PR is targeting a non-default branch and rejects the deployment', async () => {
octokit.graphql = jest.fn().mockReturnValue({
repository: {
pullRequest: {
reviewDecision: 'APPROVED',
reviews: {
totalCount: 1
},
commits: {
nodes: [
{
commit: {
oid: 'abcde12345',
checkSuites: {
totalCount: 8
},
statusCheckRollup: {
state: 'SUCCESS'
}
}
}
]
}
}
}
})
octokit.rest.pulls.get = jest.fn().mockReturnValue({
data: {
head: {
ref: 'test-ref',
sha: 'abc123'
},
repo: {
fork: false
},
base: {
ref: 'not-main'
}
},
status: 200
})

expect(await prechecks(context, octokit, data)).toStrictEqual({
message: `### ⚠️ Cannot proceed with deployment\n\nThis pull request is attempting to merge into the \`not-main\` branch which is not the default branch of this repository (\`${data.inputs.stable_branch}\`). This deployment has been rejected since it could be dangerous to proceed.`,
status: false
})

expect(setOutputMock).toHaveBeenCalledWith(
'non_default_target_branch_used',
'true'
)
})

test('runs prechecks and finds that the PR is targeting a non-default branch and allows the deployment based on the action config and logs a warning', async () => {
octokit.graphql = jest.fn().mockReturnValue({
repository: {
pullRequest: {
reviewDecision: 'APPROVED',
reviews: {
totalCount: 1
},
commits: {
nodes: [
{
commit: {
oid: 'abcde12345',
checkSuites: {
totalCount: 8
},
statusCheckRollup: {
state: 'SUCCESS'
}
}
}
]
}
}
}
})
octokit.rest.pulls.get = jest.fn().mockReturnValue({
data: {
head: {
ref: 'test-ref',
sha: 'abcde12345'
},
repo: {
fork: false
},
base: {
ref: 'not-main'
}
},
status: 200
})

data.inputs.allow_non_default_target_branch_deployments = true

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

expect(setOutputMock).toHaveBeenCalledWith(
'non_default_target_branch_used',
'true'
)

expect(warningMock).toHaveBeenCalledWith(
`🚨 this pull request is attempting to merge into the \`not-main\` branch which is not the default branch of this repository (\`${data.inputs.stable_branch}\`) - this action is potentially dangerous`
)
})

test('runs prechecks and finds that the IssueOps command is valid for a branch deployment and is from a forked repository and the PR is approved but CI is failing and it is a noop', async () => {
Expand Down
1 change: 1 addition & 0 deletions __tests__/main.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ beforeEach(() => {
process.env.INPUT_COMMIT_VERIFICATION = 'false'
process.env.INPUT_IGNORED_CHECKS = ''
process.env.INPUT_USE_SECURITY_WARNINGS = 'true'
process.env.INPUT_ALLOW_NON_DEFAULT_TARGET_BRANCH_DEPLOYMENTS = 'false'

github.context.payload = {
issue: {
Expand Down
14 changes: 14 additions & 0 deletions __tests__/schemas/action.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,16 @@ inputs:
default:
type: string
required: false
allow_non_default_target_branch_deployments:
description:
type: string
required: true
required:
type: boolean
required: true
default:
type: string
required: false

# outputs section
outputs:
Expand Down Expand Up @@ -641,3 +651,7 @@ outputs:
description:
type: string
required: true
non_default_target_branch_used:
description:
type: string
required: true
6 changes: 6 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ inputs:
description: 'Whether or not to leave security related warnings in log messages during deployments. Default is "true"'
required: false
default: "true"
allow_non_default_target_branch_deployments:
description: 'Whether or not to allow deployments of pull requests that target a branch other than the default branch (aka stable branch) as their merge target. By default, this Action would reject the deployment of a branch named "feature-branch" if it was targeting "foo" instead of "main" (or whatever your default branch is). This option allows you to override that behavior and be able to deploy any branch in your repository regardless of the target branch. This option is potentially unsafe and should be used with caution as most default branches contain branch protection rules. Often times non-default branches do not contain these same branch protection rules. Follow along in this issue thread to learn more https://github.com/github/branch-deploy/issues/340'
required: false
default: "false"
outputs:
continue:
description: 'The string "true" if the deployment should continue, otherwise empty - Use this to conditionally control if your deployment should proceed or not'
Expand Down Expand Up @@ -264,6 +268,8 @@ outputs:
description: 'The string "true" if the commit has a verified signature, otherwise "false"'
total_seconds:
description: 'The total number of seconds that the deployment took to complete (Integer)'
non_default_target_branch_used:
description: 'The string "true" if the pull request is targeting a branch other than the default branch (aka stable branch) for the merge, otherwise unset'
runs:
using: "node20"
main: "dist/index.js"
Expand Down
Loading

0 comments on commit c8417fc

Please sign in to comment.