Skip to content

Commit b020429

Browse files
authored
Merge pull request #453 from mohammad19991/main
feat: add disable_lock input to bypass deployment locking
2 parents c9449f4 + b2669b1 commit b020429

12 files changed

Lines changed: 242 additions & 87 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ As seen above, we have two steps. One for a noop deploy, and one for a regular d
292292
| `deploy_message_path` | `false` | `".github/deployment_message.md"` | The path to a markdown file which is used as a template for custom deployment messages. Example: `".github/deployment_message.md"` |
293293
| `sticky_locks` | `false` | `"false"` | If set to `"true"`, locks will not be released after a deployment run completes. This applies to both successful, and failed deployments.Sticky locks are also known as ["hubot style deployment locks"](./docs/hubot-style-deployment-locks.md). They will persist until they are manually released by a user, or if you configure [another workflow with the "unlock on merge" mode](./docs/unlock-on-merge.md) to remove them automatically on PR merge. |
294294
| `sticky_locks_for_noop` | `false` | `"false"` | If set to `"true"`, then sticky_locks will also be used for noop deployments. This can be useful in some cases but it often leads to locks being left behind when users test noop deployments. |
295+
| `disable_lock` | `false` | `"false"` | If set to `"true"`, all deployment locking is disabled. Useful for workflows where concurrent deployments are safe (e.g. iOS/Android builds uploaded to TestFlight or the Play Store). When disabled, `.lock` and `.unlock` commands will return an informational message instead of modifying lock state. See the [disable lock](docs/locks.md#disabling-locks) documentation for more details. |
295296
| `allow_sha_deployments` | `false` | `"false"` | If set to `"true"`, then you can deploy a specific sha instead of a branch. Example: `".deploy 1234567890abcdef1234567890abcdef12345678 to production"` - This is dangerous and potentially unsafe, [view the docs](docs/sha-deployments.md) to learn more |
296297
| `disable_naked_commands` | `false` | `"false"` | If set to `"true"`, then naked commands will be disabled. Example: `.deploy` will not trigger a deployment. Instead, you must use `.deploy to production` to trigger a deployment. This is useful if you want to prevent accidental deployments from happening. View the [docs](docs/naked-commands.md) to learn more |
297298
| `successful_deploy_labels` | `false` | `""` | A comma separated list of labels to add to the pull request when a deployment is successful. Example: `"deployed,success"` |

__tests__/functions/post-deploy.test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,3 +668,14 @@ test('fails due to no noop', async () => {
668668
expect(e.message).toBe('no noop value provided')
669669
}
670670
})
671+
672+
test('skips lock check and release when disable_lock is true', async () => {
673+
const lockSpy = vi.spyOn(lock, 'lock')
674+
const unlockSpy = vi.spyOn(unlock, 'unlock')
675+
676+
data.disable_lock = true
677+
678+
expect(await postDeploy(context, octokit, data)).toBe('success')
679+
expect(lockSpy).not.toHaveBeenCalled()
680+
expect(unlockSpy).not.toHaveBeenCalled()
681+
})

__tests__/main.test.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ beforeEach(() => {
8989
process.env.INPUT_UNLOCK_ON_MERGE_MODE = 'false'
9090
process.env.INPUT_STICKY_LOCKS = 'false'
9191
process.env.INPUT_STICKY_LOCKS_FOR_NOOP = 'false'
92+
process.env.INPUT_DISABLE_LOCK = 'false'
9293
process.env.INPUT_ALLOW_SHA_DEPLOYMENTS = 'false'
9394
process.env.INPUT_DISABLE_NAKED_COMMANDS = 'false'
9495
process.env.INPUT_OUTDATED_MODE = 'default_branch'
@@ -1476,3 +1477,68 @@ test('stores params and parsed params into context with complex params', async (
14761477
expect(setOutputMock).toHaveBeenCalledWith('params', params)
14771478
expect(setOutputMock).toHaveBeenCalledWith('parsed_params', parsed_params)
14781479
})
1480+
1481+
test('successfully runs the action with disable_lock enabled - skips lock acquisition on deploy', async () => {
1482+
process.env.INPUT_DISABLE_LOCK = 'true'
1483+
1484+
// clear the mock set up in beforeEach so we can assert it was not called
1485+
const lockSpy = vi.spyOn(lock, 'lock').mockClear()
1486+
1487+
expect(await run()).toBe('success')
1488+
expect(lockSpy).not.toHaveBeenCalled()
1489+
expect(setOutputMock).toHaveBeenCalledWith('triggered', 'true')
1490+
expect(setOutputMock).toHaveBeenCalledWith('type', 'deploy')
1491+
expect(saveStateMock).toHaveBeenCalledWith('disable_lock', true)
1492+
})
1493+
1494+
test('returns safe-exit and posts informational comment when disable_lock is set and .lock is triggered', async () => {
1495+
process.env.INPUT_DISABLE_LOCK = 'true'
1496+
1497+
github.context.payload.comment.body = '.lock'
1498+
1499+
vi.spyOn(validPermissions, 'validPermissions').mockImplementation(() => {
1500+
return true
1501+
})
1502+
const actionStatusSpy = vi
1503+
.spyOn(actionStatus, 'actionStatus')
1504+
.mockImplementation(() => {
1505+
return undefined
1506+
})
1507+
1508+
expect(await run()).toBe('safe-exit')
1509+
expect(setOutputMock).toHaveBeenCalledWith('type', 'lock')
1510+
expect(actionStatusSpy).toHaveBeenCalledWith(
1511+
expect.anything(),
1512+
expect.anything(),
1513+
123,
1514+
'🔓 Deployment locking is disabled for this Action — lock/unlock commands have no effect.',
1515+
true
1516+
)
1517+
expect(saveStateMock).toHaveBeenCalledWith('bypass', 'true')
1518+
})
1519+
1520+
test('returns safe-exit and posts informational comment when disable_lock is set and .unlock is triggered', async () => {
1521+
process.env.INPUT_DISABLE_LOCK = 'true'
1522+
1523+
github.context.payload.comment.body = '.unlock'
1524+
1525+
vi.spyOn(validPermissions, 'validPermissions').mockImplementation(() => {
1526+
return true
1527+
})
1528+
const actionStatusSpy = vi
1529+
.spyOn(actionStatus, 'actionStatus')
1530+
.mockImplementation(() => {
1531+
return undefined
1532+
})
1533+
1534+
expect(await run()).toBe('safe-exit')
1535+
expect(setOutputMock).toHaveBeenCalledWith('type', 'unlock')
1536+
expect(actionStatusSpy).toHaveBeenCalledWith(
1537+
expect.anything(),
1538+
expect.anything(),
1539+
123,
1540+
'🔓 Deployment locking is disabled for this Action — lock/unlock commands have no effect.',
1541+
true
1542+
)
1543+
expect(saveStateMock).toHaveBeenCalledWith('bypass', 'true')
1544+
})

__tests__/schemas/action.schema.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,16 @@ inputs:
390390
default:
391391
required: true
392392
type: string
393+
disable_lock:
394+
description:
395+
type: string
396+
required: true
397+
required:
398+
type: boolean
399+
required: true
400+
default:
401+
required: true
402+
type: string
393403
allow_sha_deployments:
394404
description:
395405
type: string

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ inputs:
149149
description: 'If set to "true", then sticky_locks will also be used for noop deployments. This can be useful in some cases but it often leads to locks being left behind when users test noop deployments.'
150150
required: false
151151
default: "false"
152+
disable_lock:
153+
description: 'If set to "true", all deployment locking is disabled. Useful for workflows where concurrent deployments are safe (e.g. iOS/Android builds uploaded to TestFlight). When disabled, .lock and .unlock commands will return an informational message instead of modifying lock state.'
154+
required: false
155+
default: "false"
152156
allow_sha_deployments:
153157
description: 'If set to "true", then you can deploy a specific sha instead of a branch. Example: ".deploy 1234567890abcdef1234567890abcdef12345678 to production" - This is dangerous and potentially unsafe, view the docs to learn more: https://github.com/github/branch-deploy/blob/main/docs/sha-deployments.md'
154158
required: false

dist/index.js

Lines changed: 64 additions & 43 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/locks.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,27 @@ Removing the global deploy lock:
8787

8888
![remove-global-deploy-lock](https://user-images.githubusercontent.com/23362539/224514485-e60605fd-0918-466e-9aab-7597fa32e7d9.png)
8989

90+
## Disabling Locks
91+
92+
For some workflows, deployment locking is simply not needed. A good example is mobile CI/CD pipelines that upload build artifacts to TestFlight or the Google Play Store — each upload is additive and independent, so two concurrent deployments can never conflict with each other. In these cases, requiring a lock before every deploy adds unnecessary friction.
93+
94+
You can completely disable all locking logic by setting the `disable_lock` input to `"true"`:
95+
96+
```yaml
97+
- uses: github/branch-deploy@vX
98+
with:
99+
disable_lock: "true"
100+
```
101+
102+
When `disable_lock` is enabled:
103+
104+
- Lock acquisition is **skipped** before a deployment starts — no lock branch is ever created
105+
- The post-deploy lock release step is **skipped**
106+
- `.lock` and `.unlock` commands respond with an informational comment and exit cleanly — lock state is never modified
107+
108+
> [!NOTE]
109+
> `disable_lock` disables locking entirely. If you want locks to persist across deployments (rather than be released automatically), see [hubot-style sticky locks](./hubot-style-deployment-locks.md) instead.
110+
90111
## Actions Concurrency
91112

92113
> Note: Using the locking mechanism included in this Action (above) is highly recommended over Actions concurrency. The section below will be included anyways should you have a valid reason to use it instead of the deploy lock features this Action provides

src/functions/inputs.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export function getInputs() {
5959
const permissions = stringToArray(core.getInput('permissions'))
6060
const sticky_locks = core.getBooleanInput('sticky_locks')
6161
const sticky_locks_for_noop = core.getBooleanInput('sticky_locks_for_noop')
62+
const disable_lock = core.getBooleanInput('disable_lock')
6263
const allow_sha_deployments = core.getBooleanInput('allow_sha_deployments')
6364
const disable_naked_commands = core.getBooleanInput('disable_naked_commands')
6465
const enforced_deployment_order = stringToArray(
@@ -123,6 +124,7 @@ export function getInputs() {
123124
param_separator: param_separator,
124125
sticky_locks: sticky_locks,
125126
sticky_locks_for_noop: sticky_locks_for_noop,
127+
disable_lock: disable_lock,
126128
enforced_deployment_order: enforced_deployment_order,
127129
commit_verification: commit_verification,
128130
ignored_checks: ignored_checks,

0 commit comments

Comments
 (0)