Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .github/workflows/build-test-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ jobs:
VORTEX_DEPLOY_BRANCH: ${{ env.DEPLOY_BRANCH || github.head_ref || github.ref_name }}
VORTEX_DEPLOY_PR: ${{ env.DEPLOY_PR_NUMBER || github.event.number }}
VORTEX_DEPLOY_PR_HEAD: ${{ env.DEPLOY_PR_HEAD_SHA || github.event.pull_request.head.sha }}
VORTEX_DEPLOY_PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
VORTEX_DEPLOY_ARTIFACT_SRC: /tmp/workspace/code
VORTEX_DEPLOY_ARTIFACT_ROOT: ${{ github.workspace }}
VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE: ${{ vars.VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE }}
Expand All @@ -681,6 +682,7 @@ jobs:
VORTEX_DEPLOY_ALLOW_SKIP: ${{ vars.VORTEX_DEPLOY_ALLOW_SKIP }}
VORTEX_DEPLOY_SKIP_PRS: ${{ vars.VORTEX_DEPLOY_SKIP_PRS }}
VORTEX_DEPLOY_SKIP_BRANCHES: ${{ vars.VORTEX_DEPLOY_SKIP_BRANCHES }}
VORTEX_DEPLOY_ALLOW_LABEL: ${{ vars.VORTEX_DEPLOY_ALLOW_LABEL }}
VORTEX_DEPLOY_ACTION: ${{ inputs.override_db && 'deploy_override_db' || '' }}
timeout-minutes: 30
#;> DEPLOYMENT
3 changes: 3 additions & 0 deletions .vortex/docs/.utils/variables/extra/ci.variables.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ VORTEX_DEPLOY_SKIP_PRS=
# Branch names to skip deployment for (single value or comma-separated list).
VORTEX_DEPLOY_SKIP_BRANCHES=

# Label that authorizes a pull request deployment. When set, a PR is deployed only if it carries this label.
VORTEX_DEPLOY_ALLOW_LABEL=

# Proceed with container image deployment after it was exported.
VORTEX_EXPORT_DB_CONTAINER_REGISTRY_DEPLOY_PROCEED=

Expand Down
32 changes: 32 additions & 0 deletions .vortex/docs/content/deployment/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,38 @@ VORTEX_DEPLOY_SKIP_PRS=42,123
VORTEX_DEPLOY_SKIP_BRANCHES=feature/test,hotfix/urgent
```

## Gating deployments on a PR label

Skipping is subtractive - everything deploys unless you exclude it. The label
gate is the opposite: it makes a pull request deployment **opt-in**, so a PR
reaches an environment only when it is explicitly labeled for it. This is
useful for controlling the number and cost of per-PR environments, or for
keeping work-in-progress pull requests out of environments.

Set `$VORTEX_DEPLOY_ALLOW_LABEL` to the name of the label that authorizes
deployment:

```shell
VORTEX_DEPLOY_ALLOW_LABEL=deploy
```

With this set, a pull request is deployed only if it carries the `deploy`
label; pull requests without it are skipped. When the variable is empty or
unset (the default), the gate is inactive and deployments behave as before.

The gate applies only to pull request builds. Branch and tag deployments have
no associated pull request label and are never gated.

The pull request's labels are read from `$VORTEX_DEPLOY_PR_LABELS`, a
comma-separated list that the CI provider populates from the pull request
event. On GitHub Actions this is wired up automatically. On CircleCI the
labels are not available natively, so populate `$VORTEX_DEPLOY_PR_LABELS`
yourself (for example, from the GitHub API) to use the gate.

The label gate and the skip lists can be combined. A skip-list match is
evaluated first and always wins, so a pull request listed in
`$VORTEX_DEPLOY_SKIP_PRS` is skipped even when it carries the gate label.

## See also

| Topic | Description |
Expand Down
1 change: 1 addition & 0 deletions .vortex/docs/content/development/variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ The list below is automatically generated with [Shellvar](https://github.com/ale
| <a id="vortex_db_image"></a>`VORTEX_DB_IMAGE` | Name of the database container image to use.<br/><br/>See https://github.com/drevops/mariadb-drupal-data to seed your DB image. | `UNDEFINED` | `.env` |
| <a id="vortex_db_image_base"></a>`VORTEX_DB_IMAGE_BASE` | Name of the database fall-back container image to use.<br/><br/>If the image specified in [`$VORTEX_DB_IMAGE`](#vortex_db_image) does not exist and base image was provided - it will be used as a "clean slate" for the database. | `UNDEFINED` | `.env` |
| <a id="vortex_debug"></a>`VORTEX_DEBUG` | Set to `1` to print debug information in Vortex scripts. | `UNDEFINED` | `.env.local.example` |
| <a id="vortex_deploy_allow_label"></a>`VORTEX_DEPLOY_ALLOW_LABEL` | Label that authorizes a pull request deployment. When set, a PR is deployed only if it carries this label. | `UNDEFINED` | `CI config` |
| <a id="vortex_deploy_allow_skip"></a>`VORTEX_DEPLOY_ALLOW_SKIP` | Flag to allow skipping of a deployment using additional flags. | `UNDEFINED` | `CI config` |
| <a id="vortex_deploy_skip"></a>`VORTEX_DEPLOY_SKIP` | Skip all deployments. | `UNDEFINED` | `CI config` |
| <a id="vortex_deploy_skip_branches"></a>`VORTEX_DEPLOY_SKIP_BRANCHES` | Branch names to skip deployment for (single value or comma-separated list). | `UNDEFINED` | `CI config` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ jobs:
VORTEX_DEPLOY_BRANCH: ${{ env.DEPLOY_BRANCH || github.head_ref || github.ref_name }}
VORTEX_DEPLOY_PR: ${{ env.DEPLOY_PR_NUMBER || github.event.number }}
VORTEX_DEPLOY_PR_HEAD: ${{ env.DEPLOY_PR_HEAD_SHA || github.event.pull_request.head.sha }}
VORTEX_DEPLOY_PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
VORTEX_DEPLOY_ARTIFACT_SRC: /tmp/workspace/code
VORTEX_DEPLOY_ARTIFACT_ROOT: ${{ github.workspace }}
VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE: ${{ vars.VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE }}
Expand All @@ -608,5 +609,6 @@ jobs:
VORTEX_DEPLOY_ALLOW_SKIP: ${{ vars.VORTEX_DEPLOY_ALLOW_SKIP }}
VORTEX_DEPLOY_SKIP_PRS: ${{ vars.VORTEX_DEPLOY_SKIP_PRS }}
VORTEX_DEPLOY_SKIP_BRANCHES: ${{ vars.VORTEX_DEPLOY_SKIP_BRANCHES }}
VORTEX_DEPLOY_ALLOW_LABEL: ${{ vars.VORTEX_DEPLOY_ALLOW_LABEL }}
VORTEX_DEPLOY_ACTION: ${{ inputs.override_db && 'deploy_override_db' || '' }}
timeout-minutes: 30
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@@ -515,98 +515,3 @@
@@ -515,100 +515,3 @@
timeout-minutes: 120 # Cancel the action after 120 minutes, regardless of whether a connection has been established.
with:
detached: true
Expand Down Expand Up @@ -85,6 +85,7 @@
- VORTEX_DEPLOY_BRANCH: ${{ env.DEPLOY_BRANCH || github.head_ref || github.ref_name }}
- VORTEX_DEPLOY_PR: ${{ env.DEPLOY_PR_NUMBER || github.event.number }}
- VORTEX_DEPLOY_PR_HEAD: ${{ env.DEPLOY_PR_HEAD_SHA || github.event.pull_request.head.sha }}
- VORTEX_DEPLOY_PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
- VORTEX_DEPLOY_ARTIFACT_SRC: /tmp/workspace/code
- VORTEX_DEPLOY_ARTIFACT_ROOT: ${{ github.workspace }}
- VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE: ${{ vars.VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE }}
Expand All @@ -95,5 +96,6 @@
- VORTEX_DEPLOY_ALLOW_SKIP: ${{ vars.VORTEX_DEPLOY_ALLOW_SKIP }}
- VORTEX_DEPLOY_SKIP_PRS: ${{ vars.VORTEX_DEPLOY_SKIP_PRS }}
- VORTEX_DEPLOY_SKIP_BRANCHES: ${{ vars.VORTEX_DEPLOY_SKIP_BRANCHES }}
- VORTEX_DEPLOY_ALLOW_LABEL: ${{ vars.VORTEX_DEPLOY_ALLOW_LABEL }}
- VORTEX_DEPLOY_ACTION: ${{ inputs.override_db && 'deploy_override_db' || '' }}
- timeout-minutes: 30
31 changes: 31 additions & 0 deletions .vortex/tooling/src/deploy
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ VORTEX_DEPLOY_PR="${VORTEX_DEPLOY_PR:-}"
# Flag to allow skipping of a deployment using additional flags.
VORTEX_DEPLOY_ALLOW_SKIP="${VORTEX_DEPLOY_ALLOW_SKIP:-}"

# Label that authorizes a pull request deployment.
#
# When set to a non-empty label name, a PR deployment proceeds only if the
# pull request carries that label; PRs without it are skipped. When empty (the
# default), the gate is inactive and deployments behave as before. Only applies
# to PR builds (VORTEX_DEPLOY_PR set); branch and tag deployments are unaffected.
VORTEX_DEPLOY_ALLOW_LABEL="${VORTEX_DEPLOY_ALLOW_LABEL:-}"

# Comma-separated list of labels present on the deployed pull request.
#
# Populated by the CI provider from the pull request event (for example, in
# GitHub Actions from "github.event.pull_request.labels"). Consulted by the
# VORTEX_DEPLOY_ALLOW_LABEL gate.
VORTEX_DEPLOY_PR_LABELS="${VORTEX_DEPLOY_PR_LABELS:-}"

# ------------------------------------------------------------------------------

# @formatter:off
Expand Down Expand Up @@ -105,6 +120,22 @@ if [ "${VORTEX_DEPLOY_ALLOW_SKIP:-}" = "1" ]; then
fi
fi

if [ -n "${VORTEX_DEPLOY_ALLOW_LABEL}" ] && [ -n "${VORTEX_DEPLOY_PR}" ]; then
# Gate a pull request deployment on the presence of a label.
#
# The PR's labels are provided as a comma-separated list in
# $VORTEX_DEPLOY_PR_LABELS, populated by the CI provider from the PR event.
note "Found flag to gate deployment on the '${VORTEX_DEPLOY_ALLOW_LABEL}' label."

if ! echo ",${VORTEX_DEPLOY_PR_LABELS}," | grep -qF ",${VORTEX_DEPLOY_ALLOW_LABEL},"; then
note "PR ${VORTEX_DEPLOY_PR} does not carry the '${VORTEX_DEPLOY_ALLOW_LABEL}' label."
note "Skipping deployment ${VORTEX_DEPLOY_TYPES}."
exit 0
fi

note "PR ${VORTEX_DEPLOY_PR} carries the '${VORTEX_DEPLOY_ALLOW_LABEL}' label."
fi

if [ -z "${VORTEX_DEPLOY_TYPES##*artifact*}" ]; then
[ "${VORTEX_DEPLOY_MODE}" = "tag" ] && export VORTEX_DEPLOY_ARTIFACT_DST_BRANCH="deployment/[tags:-]"
"${SCRIPT_DIR}/deploy-artifact"
Expand Down
168 changes: 168 additions & 0 deletions .vortex/tooling/tests/unit/deploy.bats
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,171 @@ load ../_helper.bash

popd >/dev/null
}

@test "Label gate inactive when VORTEX_DEPLOY_ALLOW_LABEL is unset" {
pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1

export VORTEX_DEPLOY_TYPES="webhook"
export VORTEX_DEPLOY_WEBHOOK_URL="https://example.com"
export VORTEX_DEPLOY_PR="123"
export VORTEX_DEPLOY_PR_LABELS="bug,enhancement"

mock_deploy_webhook=$(mock_command ".vortex/tooling/src/deploy-webhook")
mock_set_output "${mock_deploy_webhook}" "Webhook deployment completed" 0

run .vortex/tooling/src/deploy
assert_success
assert_output_not_contains "Found flag to gate deployment"
assert_output_not_contains "Skipping deployment"

popd >/dev/null
}

@test "Deployment proceeds when PR carries the required label" {
pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1

export VORTEX_DEPLOY_TYPES="webhook"
export VORTEX_DEPLOY_WEBHOOK_URL="https://example.com"
export VORTEX_DEPLOY_PR="123"
export VORTEX_DEPLOY_ALLOW_LABEL="deploy"
export VORTEX_DEPLOY_PR_LABELS="deploy"

mock_deploy_webhook=$(mock_command ".vortex/tooling/src/deploy-webhook")
mock_set_output "${mock_deploy_webhook}" "Webhook deployment completed" 0

run .vortex/tooling/src/deploy
assert_success
assert_output_contains "Found flag to gate deployment on the 'deploy' label."
assert_output_contains "PR 123 carries the 'deploy' label."
assert_output_not_contains "Skipping deployment"

popd >/dev/null
}

@test "Deployment skipped when PR lacks the required label" {
pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1

export VORTEX_DEPLOY_TYPES="webhook"
export VORTEX_DEPLOY_PR="123"
export VORTEX_DEPLOY_ALLOW_LABEL="deploy"
export VORTEX_DEPLOY_PR_LABELS="bug,enhancement"

run .vortex/tooling/src/deploy
assert_success
assert_output_contains "Found flag to gate deployment on the 'deploy' label."
assert_output_contains "PR 123 does not carry the 'deploy' label."
assert_output_contains "Skipping deployment webhook."

popd >/dev/null
}

@test "Label gate does not apply to non-PR deployments" {
pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1

export VORTEX_DEPLOY_TYPES="webhook"
export VORTEX_DEPLOY_WEBHOOK_URL="https://example.com"
export VORTEX_DEPLOY_ALLOW_LABEL="deploy"
export VORTEX_DEPLOY_PR=""
export VORTEX_DEPLOY_BRANCH="main"

mock_deploy_webhook=$(mock_command ".vortex/tooling/src/deploy-webhook")
mock_set_output "${mock_deploy_webhook}" "Webhook deployment completed" 0

run .vortex/tooling/src/deploy
assert_success
assert_output_not_contains "Found flag to gate deployment"
assert_output_not_contains "Skipping deployment"

popd >/dev/null
}

@test "Deployment proceeds when required label is among several PR labels" {
pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1

export VORTEX_DEPLOY_TYPES="webhook"
export VORTEX_DEPLOY_WEBHOOK_URL="https://example.com"
export VORTEX_DEPLOY_PR="456"
export VORTEX_DEPLOY_ALLOW_LABEL="deploy"
export VORTEX_DEPLOY_PR_LABELS="bug,deploy,enhancement"

mock_deploy_webhook=$(mock_command ".vortex/tooling/src/deploy-webhook")
mock_set_output "${mock_deploy_webhook}" "Webhook deployment completed" 0

run .vortex/tooling/src/deploy
assert_success
assert_output_contains "PR 456 carries the 'deploy' label."
assert_output_not_contains "Skipping deployment"

popd >/dev/null
}

@test "Skip list takes precedence over the label gate" {
pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1

export VORTEX_DEPLOY_TYPES="webhook"
export VORTEX_DEPLOY_ALLOW_SKIP="1"
export VORTEX_DEPLOY_PR="123"
export VORTEX_DEPLOY_SKIP_PRS="123"
export VORTEX_DEPLOY_ALLOW_LABEL="deploy"
export VORTEX_DEPLOY_PR_LABELS="deploy"

run .vortex/tooling/src/deploy
assert_success
assert_output_contains "Found PR 123 in skip list."
assert_output_contains "Skipping deployment webhook."
assert_output_not_contains "Found flag to gate deployment"

popd >/dev/null
}

@test "Deployment skipped when PR has no labels and gate is enabled" {
pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1

export VORTEX_DEPLOY_TYPES="webhook"
export VORTEX_DEPLOY_PR="123"
export VORTEX_DEPLOY_ALLOW_LABEL="deploy"
export VORTEX_DEPLOY_PR_LABELS=""

run .vortex/tooling/src/deploy
assert_success
assert_output_contains "PR 123 does not carry the 'deploy' label."
assert_output_contains "Skipping deployment webhook."

popd >/dev/null
}

@test "Label gate requires an exact match, not a partial one" {
pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1

export VORTEX_DEPLOY_TYPES="webhook"
export VORTEX_DEPLOY_PR="123"
export VORTEX_DEPLOY_ALLOW_LABEL="deploy"
export VORTEX_DEPLOY_PR_LABELS="deploy-preview,needs-deploy"

run .vortex/tooling/src/deploy
assert_success
assert_output_contains "PR 123 does not carry the 'deploy' label."
assert_output_contains "Skipping deployment webhook."

popd >/dev/null
}

@test "Label gate matches a label that contains spaces" {
pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1

export VORTEX_DEPLOY_TYPES="webhook"
export VORTEX_DEPLOY_WEBHOOK_URL="https://example.com"
export VORTEX_DEPLOY_PR="123"
export VORTEX_DEPLOY_ALLOW_LABEL="deploy to env"
export VORTEX_DEPLOY_PR_LABELS="bug,deploy to env,enhancement"

mock_deploy_webhook=$(mock_command ".vortex/tooling/src/deploy-webhook")
mock_set_output "${mock_deploy_webhook}" "Webhook deployment completed" 0

run .vortex/tooling/src/deploy
assert_success
assert_output_contains "PR 123 carries the 'deploy to env' label."
assert_output_not_contains "Skipping deployment"

popd >/dev/null
}