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

Enforced Deployment Order #304

Merged
merged 28 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d3d43fd
adding new `enforced_deployment_order` action input
GrantBirki Sep 19, 2024
ee74d80
fix typo
GrantBirki Sep 19, 2024
32fedf1
code grouping for clarity
GrantBirki Sep 19, 2024
7bca69c
add a helper method for fetching the latest deployment for a given env
GrantBirki Sep 19, 2024
b455809
enrich the graphql query a bit more for fetching the latest deployment
GrantBirki Sep 19, 2024
1217cb7
bake the sha into the deployment payload for later reference
GrantBirki Sep 19, 2024
f7ad4b4
improving deployment method tests
GrantBirki Sep 19, 2024
34ef471
complete test coverage for `latestDeployment()`
GrantBirki Sep 19, 2024
9212a28
add a helper method for checking if the latest deployment is active f…
GrantBirki Sep 19, 2024
11be367
update comment
GrantBirki Sep 19, 2024
e82ece7
update the help message to reflect changes to `enforced_deployment_or…
GrantBirki Sep 19, 2024
7afa7e9
add `validDeploymentOrder` logic
GrantBirki Sep 19, 2024
cc56efc
map on `.results`
GrantBirki Sep 20, 2024
1070c66
100% test coverage
GrantBirki Sep 20, 2024
bac7b0e
add new output `needs_to_be_deployed` to determine which envs need ac…
GrantBirki Sep 20, 2024
2fc371f
tight join for logs
GrantBirki Sep 20, 2024
dd14a0f
update outputs
GrantBirki Sep 20, 2024
295a4b9
docs
GrantBirki Sep 20, 2024
7ec097b
remove duplicate log message
GrantBirki Sep 20, 2024
e239553
adding documentation
GrantBirki Sep 20, 2024
18d0b5c
update usage guide
GrantBirki Sep 20, 2024
8283dd4
clean up deployment order message
GrantBirki Sep 20, 2024
13863c0
bypass all enforced order checks in the event of a rollback deploy
GrantBirki Sep 20, 2024
7bd4a2b
add rollback note
GrantBirki Sep 20, 2024
d7dd49c
Update src/functions/valid-deployment-order.js
GrantBirki Sep 20, 2024
f04b10b
re-bundle
GrantBirki Sep 20, 2024
05a16b7
unit test improvements
GrantBirki Sep 20, 2024
d98f78d
more test improvements
GrantBirki Sep 20, 2024
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ As seen above, we have two steps. One for a noop deploy, and one for a regular d
| `environment_targets` | `false` | `production,development,staging` | Optional (or additional) target environments to select for use with deployments. Example, "production,development,staging". Example usage: `.deploy to development`, `.deploy to production`, `.deploy to staging` |
| `environment_urls` | `false` | `""` | Optional target environment URLs to use with deployments. This input option is a mapping of environment names to URLs and the environment names **must** match the `environment_targets` input option. This option is a comma separated list with pipes (`\|`) separating the environment from the URL. Note: `disabled` is a special keyword to disable an environment url if you enable this option. Format: `"<environment1>\|<url1>,<environment2>\|<url2>,etc"` Example: `"production\|https://myapp.com,development\|https://dev.myapp.com,staging\|disabled"` - See the [environment urls](#environment-urls) section for more details |
| `draft_permitted_targets` | `false` | `""` | Optional environments which can allow "draft" pull requests to be deployed. By default, this input option is empty and no environments allow deployments sourced from a pull request in a "draft" state. Examples: `"development,staging"` |
| `environment_url_in_comment` | `false` | `"true` | If the `environment_url` detected in the deployment should be appended to the successful deployment comment or not. Examples: `"true"` or `"false"` - See the [environment urls](#environment-urls) section for more details |
| `environment_url_in_comment` | `false` | `"true"` | If the `environment_url` detected in the deployment should be appended to the successful deployment comment or not. Examples: `"true"` or `"false"` - See the [environment urls](#environment-urls) section for more details |
| `production_environments` | `false` | `production` | A comma separated list of environments that should be treated as "production". GitHub defines "production" as an environment that end users or systems interact with. Example: "production,production-eu". By default, GitHub will set the "production_environment" to "true" if the environment name is "production". This option allows you to override that behavior so you can use "prod", "prd", "main", "production-eu", etc. as your production environment name. ref: [#208](https://github.com/github/branch-deploy/issues/208) |
| `stable_branch` | `false` | `main` | The name of a stable branch to deploy to (rollbacks). Example: "main" |
| `update_branch` | `false` | `warn` | Determine how you want this Action to handle "out-of-date" branches. Available options: "disabled", "warn", "force". "disabled" means that the Action will not care if a branch is out-of-date. "warn" means that the Action will warn the user that a branch is out-of-date and exit without deploying. "force" means that the Action will force update the branch. Note: The "force" option is not recommended due to Actions not being able to re-run CI on commits originating from Actions itself |
Expand All @@ -295,6 +295,7 @@ As seen above, we have two steps. One for a noop deploy, and one for a regular d
| `failed_noop_labels` | `false` | `""` | A comma separated list of labels to add to the pull request when a noop deployment fails. Example: `"failed,noop-failed"` |
| `skip_successful_noop_labels_if_approved` | `false` | `"false"` | Whether or not the post run logic should skip adding successful noop labels if the pull request is approved. This can be useful if you add a label such as "ready-for-review" after a `.noop` completes but want to skip adding that label in situations where the pull request is already approved. |
| `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 |

## Outputs 📤

Expand Down Expand Up @@ -332,6 +333,7 @@ As seen above, we have two steps. One for a noop deploy, and one for a regular d
| `merge_state_status` | The status of the merge state. Can be one of a few values - examples: `"DIRTY"`, `"DRAFT"`, `"CLEAN"`, etc |
| `commit_status` | The status of the commit. Can be one of a few values - examples: `"SUCCESS"`, `null`, `"skip_ci"`, `"PENDING"`, `"FAILURE"` etc |
| `approved_reviews_count` | The number of approved reviews on the pull request |
| `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 |

## Custom Deployment Messages ✏️

Expand Down
135 changes: 134 additions & 1 deletion __tests__/functions/deployment.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import {createDeploymentStatus} from '../../src/functions/deployment'
import {
createDeploymentStatus,
latestDeployment,
activeDeployment
} from '../../src/functions/deployment'

var octokit
var context
var mockDeploymentData
var mockDeploymentResults
beforeEach(() => {
jest.clearAllMocks()
process.env.GITHUB_SERVER_URL = 'https://github.com'
Expand All @@ -19,6 +25,52 @@ beforeEach(() => {
runId: 12345
}

mockDeploymentData = {
repository: {
deployments: {
nodes: [
{
createdAt: '2024-09-19T20:18:18Z',
environment: 'production',
updatedAt: '2024-09-19T20:18:21Z',
id: 'DE_kwDOID9x8M5sC6QZ',
payload:
'{"type":"branch-deploy", "sha": "315cec138fc9d7dac8a47c6bba4217d3965ede3b"}',
state: 'ACTIVE',
creator: {
login: 'github-actions'
},
ref: {
name: 'main'
},
commit: {
oid: '315cec138fc9d7dac8a47c6bba4217d3965ede3b'
}
}
]
}
}
}

mockDeploymentResults = {
createdAt: '2024-09-19T20:18:18Z',
environment: 'production',
updatedAt: '2024-09-19T20:18:21Z',
id: 'DE_kwDOID9x8M5sC6QZ',
payload:
'{"type":"branch-deploy", "sha": "315cec138fc9d7dac8a47c6bba4217d3965ede3b"}',
state: 'ACTIVE',
creator: {
login: 'github-actions'
},
ref: {
name: 'main'
},
commit: {
oid: '315cec138fc9d7dac8a47c6bba4217d3965ede3b'
}
}

octokit = {
rest: {
repos: {
Expand All @@ -35,6 +87,10 @@ const deploymentId = 123
const ref = 'test-ref'
const logUrl = 'https://github.com/corp/test/actions/runs/12345'

const createMockGraphQLOctokit = data => ({
graphql: jest.fn().mockReturnValueOnce(data)
})

test('creates an in_progress deployment status', async () => {
expect(
await createDeploymentStatus(
Expand All @@ -58,3 +114,80 @@ test('creates an in_progress deployment status', async () => {
log_url: logUrl
})
})

test('successfully fetches the latest deployment', async () => {
octokit = createMockGraphQLOctokit(mockDeploymentData)

expect(await latestDeployment(octokit, context, environment)).toStrictEqual(
mockDeploymentResults
)

expect(octokit.graphql).toHaveBeenCalled()
})

test('returns null if no deployments are found', async () => {
octokit = createMockGraphQLOctokit({
repository: {
deployments: {
nodes: []
}
}
})

expect(await latestDeployment(octokit, context, environment)).toBeNull()

expect(octokit.graphql).toHaveBeenCalled()
})

test('returns false if the deployment is not active', async () => {
mockDeploymentData.repository.deployments.nodes[0].state = 'INACTIVE'
octokit = createMockGraphQLOctokit(mockDeploymentData)

expect(await activeDeployment(octokit, context, environment, 'sha')).toBe(
false
)

expect(octokit.graphql).toHaveBeenCalled()
})

test('returns false if the deployment does not match the sha', async () => {
mockDeploymentData.repository.deployments.nodes[0].commit.oid = 'badsha'
octokit = createMockGraphQLOctokit(mockDeploymentData)

expect(await activeDeployment(octokit, context, environment, 'sha')).toBe(
false
)

expect(octokit.graphql).toHaveBeenCalled()
})

test('returns true if the deployment is active and matches the sha', async () => {
octokit = createMockGraphQLOctokit(mockDeploymentData)

expect(
await activeDeployment(
octokit,
context,
environment,
'315cec138fc9d7dac8a47c6bba4217d3965ede3b'
)
).toBe(true)

expect(octokit.graphql).toHaveBeenCalled()
})

test('returns false if the deployment is not found', async () => {
octokit = createMockGraphQLOctokit({
repository: {
deployments: {
nodes: []
}
}
})

expect(await activeDeployment(octokit, context, environment, 'sha')).toBe(
false
)

expect(octokit.graphql).toHaveBeenCalled()
})
22 changes: 18 additions & 4 deletions __tests__/functions/help.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ const defaultInputs = {
admins: 'false',
permissions: ['write', 'admin', 'maintain'],
allow_sha_deployments: false,
checks: 'all'
checks: 'all',
enforced_deployment_order: []
}

test('successfully calls help with defaults', async () => {
Expand Down Expand Up @@ -81,7 +82,8 @@ test('successfully calls help with non-defaults', async () => {
admins: 'monalisa',
permissions: ['write', 'admin', 'maintain'],
allow_sha_deployments: true,
checks: 'all'
checks: 'all',
enforced_deployment_order: []
}

expect(await help(octokit, context, 123, inputs))
Expand Down Expand Up @@ -115,7 +117,8 @@ test('successfully calls help with non-defaults', async () => {
admins: 'monalisa',
permissions: ['write', 'admin', 'maintain'],
allow_sha_deployments: false,
checks: 'required'
checks: 'required',
enforced_deployment_order: ['development', 'staging', 'production']
}

expect(await help(octokit, context, 123, inputs))
Expand All @@ -124,6 +127,10 @@ test('successfully calls help with non-defaults', async () => {
expect.stringMatching(/## 📚 Branch Deployment Help/)
)

expect(debugMock).toHaveBeenCalledWith(
expect.stringMatching(/a specific deployment order by environment/)
)

var inputsSecond = inputs
inputsSecond.update_branch = 'disabled'
expect(await help(octokit, context, 123, inputsSecond))
Expand Down Expand Up @@ -157,7 +164,8 @@ test('successfully calls help with non-defaults and unknown update_branch settin
admins: 'monalisa',
permissions: ['write', 'admin', 'maintain'],
allow_sha_deployments: false,
checks: 'required'
checks: 'required',
enforced_deployment_order: []
}

expect(await help(octokit, context, 123, inputs))
Expand All @@ -166,6 +174,12 @@ test('successfully calls help with non-defaults and unknown update_branch settin
expect.stringMatching(/## 📚 Branch Deployment Help/)
)

expect(debugMock).toHaveBeenCalledWith(
expect.stringMatching(
/Deployments can be made to any environment in any order/
)
)

expect(debugMock).toHaveBeenCalledWith(
expect.stringMatching(/Unknown value for update_branch/)
)
Expand Down
Loading