Skip to content

Commit c8417fc

Browse files
authored
Merge pull request #361 from github/feat/allow_non_default_target_branch_deployments
feat: allow non-default target branch deployments via a new input option
2 parents 8ee0839 + a9d963b commit c8417fc

12 files changed

+312
-20
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ As seen above, we have two steps. One for a noop deploy, and one for a regular d
300300
| `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. |
301301
| `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 |
302302
| `use_security_warnings` | `false` | `"true"` | Whether or not to leave security related warnings in log messages during deployments. Default is `"true"` |
303+
| `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. |
303304

304305
## Outputs 📤
305306

@@ -342,6 +343,7 @@ As seen above, we have two steps. One for a noop deploy, and one for a regular d
342343
| `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 |
343344
| `commit_verified` | The string `"true"` if the commit is verified, otherwise `"false"` |
344345
| `total_seconds` | The total number of seconds that the deployment took to complete (Integer) |
346+
| `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 |
345347

346348
## Custom Deployment Messages ✏️
347349

__tests__/functions/help.test.js

+13-4
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ const defaultInputs = {
5050
commit_verification: true,
5151
ignored_checks: [],
5252
enforced_deployment_order: [],
53-
use_security_warnings: true
53+
use_security_warnings: true,
54+
allow_non_default_target_branch_deployments: false
5455
}
5556

5657
test('successfully calls help with defaults', async () => {
@@ -89,7 +90,8 @@ test('successfully calls help with non-defaults', async () => {
8990
ignored_checks: ['lint', 'format'],
9091
commit_verification: false,
9192
enforced_deployment_order: [],
92-
use_security_warnings: false
93+
use_security_warnings: false,
94+
allow_non_default_target_branch_deployments: false
9395
}
9496

9597
expect(await help(octokit, context, 123, inputs))
@@ -127,7 +129,8 @@ test('successfully calls help with non-defaults again', async () => {
127129
ignored_checks: ['lint'],
128130
commit_verification: false,
129131
enforced_deployment_order: ['development', 'staging', 'production'],
130-
use_security_warnings: false
132+
use_security_warnings: false,
133+
allow_non_default_target_branch_deployments: false
131134
}
132135

133136
expect(await help(octokit, context, 123, inputs))
@@ -176,7 +179,8 @@ test('successfully calls help with non-defaults and unknown update_branch settin
176179
checks: 'required',
177180
ignored_checks: ['lint'],
178181
enforced_deployment_order: [],
179-
use_security_warnings: false
182+
use_security_warnings: false,
183+
allow_non_default_target_branch_deployments: true
180184
}
181185

182186
expect(await help(octokit, context, 123, inputs))
@@ -197,4 +201,9 @@ test('successfully calls help with non-defaults and unknown update_branch settin
197201
expect(debugMock).toHaveBeenCalledWith(
198202
expect.stringMatching(/not use security warnings/)
199203
)
204+
expect(debugMock).toHaveBeenCalledWith(
205+
expect.stringMatching(
206+
/will allow the deployments of pull requests that target a branch other than the default branch/
207+
)
208+
)
200209
})

__tests__/functions/prechecks.test.js

+189-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ beforeEach(() => {
5959
checks: 'all',
6060
permissions: ['admin', 'write'],
6161
commit_verification: false,
62-
ignored_checks: []
62+
ignored_checks: [],
63+
use_security_warnings: true,
64+
allow_non_default_target_branch_deployments: false
6365
}
6466
}
6567

@@ -1033,6 +1035,11 @@ test('runs prechecks and finds that the IssueOps command is valid for a branch d
10331035
sha: 'abcde12345',
10341036
isFork: true
10351037
})
1038+
1039+
expect(setOutputMock).not.toHaveBeenCalledWith(
1040+
'non_default_target_branch_used',
1041+
'true'
1042+
)
10361043
})
10371044

10381045
test('runs prechecks and finds that the PR from a fork is targeting a non-default branch and rejects the deployment', async () => {
@@ -1081,6 +1088,187 @@ test('runs prechecks and finds that the PR from a fork is targeting a non-defaul
10811088
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.`,
10821089
status: false
10831090
})
1091+
1092+
expect(setOutputMock).toHaveBeenCalledWith(
1093+
'non_default_target_branch_used',
1094+
'true'
1095+
)
1096+
})
1097+
1098+
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 () => {
1099+
octokit.graphql = jest.fn().mockReturnValue({
1100+
repository: {
1101+
pullRequest: {
1102+
reviewDecision: 'APPROVED',
1103+
reviews: {
1104+
totalCount: 1
1105+
},
1106+
commits: {
1107+
nodes: [
1108+
{
1109+
commit: {
1110+
oid: 'abcde12345',
1111+
checkSuites: {
1112+
totalCount: 8
1113+
},
1114+
statusCheckRollup: {
1115+
state: 'SUCCESS'
1116+
}
1117+
}
1118+
}
1119+
]
1120+
}
1121+
}
1122+
}
1123+
})
1124+
octokit.rest.pulls.get = jest.fn().mockReturnValue({
1125+
data: {
1126+
head: {
1127+
sha: 'abcde12345',
1128+
ref: 'test-ref',
1129+
label: 'test-repo:test-ref',
1130+
repo: {
1131+
fork: true
1132+
}
1133+
},
1134+
base: {
1135+
ref: 'some-other-branch'
1136+
}
1137+
},
1138+
status: 200
1139+
})
1140+
1141+
data.inputs.allow_non_default_target_branch_deployments = true
1142+
1143+
expect(await prechecks(context, octokit, data)).toStrictEqual({
1144+
message: `✅ PR is approved and all CI checks passed`,
1145+
status: true,
1146+
noopMode: false,
1147+
ref: 'abcde12345',
1148+
sha: 'abcde12345',
1149+
isFork: true
1150+
})
1151+
1152+
expect(setOutputMock).toHaveBeenCalledWith(
1153+
'non_default_target_branch_used',
1154+
'true'
1155+
)
1156+
})
1157+
1158+
test('runs prechecks and finds that the PR is targeting a non-default branch and rejects the deployment', async () => {
1159+
octokit.graphql = jest.fn().mockReturnValue({
1160+
repository: {
1161+
pullRequest: {
1162+
reviewDecision: 'APPROVED',
1163+
reviews: {
1164+
totalCount: 1
1165+
},
1166+
commits: {
1167+
nodes: [
1168+
{
1169+
commit: {
1170+
oid: 'abcde12345',
1171+
checkSuites: {
1172+
totalCount: 8
1173+
},
1174+
statusCheckRollup: {
1175+
state: 'SUCCESS'
1176+
}
1177+
}
1178+
}
1179+
]
1180+
}
1181+
}
1182+
}
1183+
})
1184+
octokit.rest.pulls.get = jest.fn().mockReturnValue({
1185+
data: {
1186+
head: {
1187+
ref: 'test-ref',
1188+
sha: 'abc123'
1189+
},
1190+
repo: {
1191+
fork: false
1192+
},
1193+
base: {
1194+
ref: 'not-main'
1195+
}
1196+
},
1197+
status: 200
1198+
})
1199+
1200+
expect(await prechecks(context, octokit, data)).toStrictEqual({
1201+
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.`,
1202+
status: false
1203+
})
1204+
1205+
expect(setOutputMock).toHaveBeenCalledWith(
1206+
'non_default_target_branch_used',
1207+
'true'
1208+
)
1209+
})
1210+
1211+
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 () => {
1212+
octokit.graphql = jest.fn().mockReturnValue({
1213+
repository: {
1214+
pullRequest: {
1215+
reviewDecision: 'APPROVED',
1216+
reviews: {
1217+
totalCount: 1
1218+
},
1219+
commits: {
1220+
nodes: [
1221+
{
1222+
commit: {
1223+
oid: 'abcde12345',
1224+
checkSuites: {
1225+
totalCount: 8
1226+
},
1227+
statusCheckRollup: {
1228+
state: 'SUCCESS'
1229+
}
1230+
}
1231+
}
1232+
]
1233+
}
1234+
}
1235+
}
1236+
})
1237+
octokit.rest.pulls.get = jest.fn().mockReturnValue({
1238+
data: {
1239+
head: {
1240+
ref: 'test-ref',
1241+
sha: 'abcde12345'
1242+
},
1243+
repo: {
1244+
fork: false
1245+
},
1246+
base: {
1247+
ref: 'not-main'
1248+
}
1249+
},
1250+
status: 200
1251+
})
1252+
1253+
data.inputs.allow_non_default_target_branch_deployments = true
1254+
1255+
expect(await prechecks(context, octokit, data)).toStrictEqual({
1256+
message: `✅ PR is approved and all CI checks passed`,
1257+
status: true,
1258+
noopMode: false,
1259+
ref: 'test-ref',
1260+
sha: 'abcde12345',
1261+
isFork: false
1262+
})
1263+
1264+
expect(setOutputMock).toHaveBeenCalledWith(
1265+
'non_default_target_branch_used',
1266+
'true'
1267+
)
1268+
1269+
expect(warningMock).toHaveBeenCalledWith(
1270+
`🚨 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`
1271+
)
10841272
})
10851273

10861274
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 () => {

__tests__/main.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ beforeEach(() => {
9090
process.env.INPUT_COMMIT_VERIFICATION = 'false'
9191
process.env.INPUT_IGNORED_CHECKS = ''
9292
process.env.INPUT_USE_SECURITY_WARNINGS = 'true'
93+
process.env.INPUT_ALLOW_NON_DEFAULT_TARGET_BRANCH_DEPLOYMENTS = 'false'
9394

9495
github.context.payload = {
9596
issue: {

__tests__/schemas/action.schema.yml

+14
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,16 @@ inputs:
490490
default:
491491
type: string
492492
required: false
493+
allow_non_default_target_branch_deployments:
494+
description:
495+
type: string
496+
required: true
497+
required:
498+
type: boolean
499+
required: true
500+
default:
501+
type: string
502+
required: false
493503

494504
# outputs section
495505
outputs:
@@ -641,3 +651,7 @@ outputs:
641651
description:
642652
type: string
643653
required: true
654+
non_default_target_branch_used:
655+
description:
656+
type: string
657+
required: true

action.yml

+6
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ inputs:
189189
description: 'Whether or not to leave security related warnings in log messages during deployments. Default is "true"'
190190
required: false
191191
default: "true"
192+
allow_non_default_target_branch_deployments:
193+
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'
194+
required: false
195+
default: "false"
192196
outputs:
193197
continue:
194198
description: 'The string "true" if the deployment should continue, otherwise empty - Use this to conditionally control if your deployment should proceed or not'
@@ -264,6 +268,8 @@ outputs:
264268
description: 'The string "true" if the commit has a verified signature, otherwise "false"'
265269
total_seconds:
266270
description: 'The total number of seconds that the deployment took to complete (Integer)'
271+
non_default_target_branch_used:
272+
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'
267273
runs:
268274
using: "node20"
269275
main: "dist/index.js"

0 commit comments

Comments
 (0)