Skip to content

Commit

Permalink
Merge pull request #304 from github/enforced-deployment-order
Browse files Browse the repository at this point in the history
Enforced Deployment Order
  • Loading branch information
GrantBirki authored Sep 20, 2024
2 parents 85439e7 + d98f78d commit a5f65d4
Show file tree
Hide file tree
Showing 16 changed files with 1,138 additions and 15 deletions.
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

0 comments on commit a5f65d4

Please sign in to comment.