Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ As seen above, we have two steps. One for a noop deploy, and one for a regular d
| `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"` |
| `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. |
| `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. |
| `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. |
| `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 |
| `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 |
| `successful_deploy_labels` | `false` | `""` | A comma separated list of labels to add to the pull request when a deployment is successful. Example: `"deployed,success"` |
Expand Down
11 changes: 11 additions & 0 deletions __tests__/functions/post-deploy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -668,3 +668,14 @@ test('fails due to no noop', async () => {
expect(e.message).toBe('no noop value provided')
}
})

test('skips lock check and release when disable_lock is true', async () => {
const lockSpy = vi.spyOn(lock, 'lock')
const unlockSpy = vi.spyOn(unlock, 'unlock')

data.disable_lock = true

expect(await postDeploy(context, octokit, data)).toBe('success')
expect(lockSpy).not.toHaveBeenCalled()
expect(unlockSpy).not.toHaveBeenCalled()
})
66 changes: 66 additions & 0 deletions __tests__/main.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ beforeEach(() => {
process.env.INPUT_UNLOCK_ON_MERGE_MODE = 'false'
process.env.INPUT_STICKY_LOCKS = 'false'
process.env.INPUT_STICKY_LOCKS_FOR_NOOP = 'false'
process.env.INPUT_DISABLE_LOCK = 'false'
process.env.INPUT_ALLOW_SHA_DEPLOYMENTS = 'false'
process.env.INPUT_DISABLE_NAKED_COMMANDS = 'false'
process.env.INPUT_OUTDATED_MODE = 'default_branch'
Expand Down Expand Up @@ -1476,3 +1477,68 @@ test('stores params and parsed params into context with complex params', async (
expect(setOutputMock).toHaveBeenCalledWith('params', params)
expect(setOutputMock).toHaveBeenCalledWith('parsed_params', parsed_params)
})

test('successfully runs the action with disable_lock enabled - skips lock acquisition on deploy', async () => {
process.env.INPUT_DISABLE_LOCK = 'true'

// clear the mock set up in beforeEach so we can assert it was not called
const lockSpy = vi.spyOn(lock, 'lock').mockClear()

expect(await run()).toBe('success')
expect(lockSpy).not.toHaveBeenCalled()
expect(setOutputMock).toHaveBeenCalledWith('triggered', 'true')
expect(setOutputMock).toHaveBeenCalledWith('type', 'deploy')
expect(saveStateMock).toHaveBeenCalledWith('disable_lock', true)
})

test('returns safe-exit and posts informational comment when disable_lock is set and .lock is triggered', async () => {
process.env.INPUT_DISABLE_LOCK = 'true'

github.context.payload.comment.body = '.lock'

vi.spyOn(validPermissions, 'validPermissions').mockImplementation(() => {
return true
})
const actionStatusSpy = vi
.spyOn(actionStatus, 'actionStatus')
.mockImplementation(() => {
return undefined
})

expect(await run()).toBe('safe-exit')
expect(setOutputMock).toHaveBeenCalledWith('type', 'lock')
expect(actionStatusSpy).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
123,
'🔓 Deployment locking is disabled for this Action — lock/unlock commands have no effect.',
true
)
expect(saveStateMock).toHaveBeenCalledWith('bypass', 'true')
})

test('returns safe-exit and posts informational comment when disable_lock is set and .unlock is triggered', async () => {
process.env.INPUT_DISABLE_LOCK = 'true'

github.context.payload.comment.body = '.unlock'

vi.spyOn(validPermissions, 'validPermissions').mockImplementation(() => {
return true
})
const actionStatusSpy = vi
.spyOn(actionStatus, 'actionStatus')
.mockImplementation(() => {
return undefined
})

expect(await run()).toBe('safe-exit')
expect(setOutputMock).toHaveBeenCalledWith('type', 'unlock')
expect(actionStatusSpy).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
123,
'🔓 Deployment locking is disabled for this Action — lock/unlock commands have no effect.',
true
)
expect(saveStateMock).toHaveBeenCalledWith('bypass', 'true')
})
10 changes: 10 additions & 0 deletions __tests__/schemas/action.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,16 @@ inputs:
default:
required: true
type: string
disable_lock:
description:
type: string
required: true
required:
type: boolean
required: true
default:
required: true
type: string
allow_sha_deployments:
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 @@ -149,6 +149,10 @@ inputs:
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.'
required: false
default: "false"
disable_lock:
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.'
required: false
default: "false"
allow_sha_deployments:
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'
required: false
Expand Down
107 changes: 64 additions & 43 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 docs/locks.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,27 @@ Removing the global deploy lock:

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

## Disabling Locks

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.

You can completely disable all locking logic by setting the `disable_lock` input to `"true"`:

```yaml
- uses: github/branch-deploy@vX
with:
disable_lock: "true"
```

When `disable_lock` is enabled:

- Lock acquisition is **skipped** before a deployment starts — no lock branch is ever created
- The post-deploy lock release step is **skipped**
- `.lock` and `.unlock` commands respond with an informational comment and exit cleanly — lock state is never modified

> [!NOTE]
> `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.

## Actions Concurrency

> 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
Expand Down
2 changes: 2 additions & 0 deletions src/functions/inputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export function getInputs() {
const permissions = stringToArray(core.getInput('permissions'))
const sticky_locks = core.getBooleanInput('sticky_locks')
const sticky_locks_for_noop = core.getBooleanInput('sticky_locks_for_noop')
const disable_lock = core.getBooleanInput('disable_lock')
const allow_sha_deployments = core.getBooleanInput('allow_sha_deployments')
const disable_naked_commands = core.getBooleanInput('disable_naked_commands')
const enforced_deployment_order = stringToArray(
Expand Down Expand Up @@ -123,6 +124,7 @@ export function getInputs() {
param_separator: param_separator,
sticky_locks: sticky_locks,
sticky_locks_for_noop: sticky_locks_for_noop,
disable_lock: disable_lock,
enforced_deployment_order: enforced_deployment_order,
commit_verification: commit_verification,
ignored_checks: ignored_checks,
Expand Down
Loading