Skip to main content

Template

This page covers maintaining the Vortex template: authoring scripts and testing the template functionality.

Authoring scripts

info

Heads up! Scripts are changing from Bash to PHP in 2.0 release. Track the progress in this issue.

Requirements

note

Please refer to RFC2119 for meaning of words MUST, SHOULD and MAY.

  1. MUST adhere to POSIX standard.
  2. MUST pass Shellcheck code analysis scan
  3. MUST start with:
    #!/usr/bin/env bash
    ##
    # Action description that the script performs.
    #
    # More description and usage information with a last empty
    # comment line.
    #

    set -eu
    [ "${VORTEX_DEBUG-}" = "1" ] && set -x
  4. MUST list all variables with their default values and descriptions. i.e.:
    # Deployment reference, such as a git SHA.
    VORTEX_NOTIFY_REF="${VORTEX_NOTIFY_REF:-}"
  5. MUST include a delimiter between variables and the script body preceded and followed by an empty line (3 lines in total):
    # ------------------------------------------------------------------------------
  6. SHOULD include formatting helper functions:
    # @formatter:off
    note() { printf " %s\n" "${1}"; }
    info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[INFO] %s\033[0m\n" "${1}" || printf "[INFO] %s\n" "${1}"; }
    pass() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s\033[0m\n" "${1}" || printf "[ OK ] %s\n" "${1}"; }
    fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; }
    # @formatter:on
  7. SHOULD include variable values checks with errors and early exist, i.e.:
    [ -z "${VORTEX_NOTIFY_REF}" ] && fail "Missing required value for VORTEX_NOTIFY_REF" && exit 1
  8. SHOULD include binaries checks if the script relies on them, i.e.:
    command -v curl > /dev/null || ( fail "curl command is not available." && exit 1 )
  9. MUST contain an info message about the start of the script body, e.g.:
    info "Started GitHub notification for operation ${VORTEX_NOTIFY_EVENT}"
  10. MUST contain an pass message about the finish of the script body, e.g.:
    pass "Finished GitHub notification for operation ${VORTEX_NOTIFY_EVENT}"
  11. MUST use uppercase global variables
  12. MUST use lowercase local variables.
  13. MUST use long options instead of short options for readability. I.e., drush cache:rebuild instead of drush cr.
  14. MUST use VORTEX_ prefix for variables, unless it is a known 3-rd party variable like PACKAGE_TOKEN or COMPOSER.
  15. MUST use script-specific prefix. I.e., for notify.sh, the variable to skip notifications should start with VORTEX_NOTIFY_.
  16. MAY rely on variables from the external scripts (not prefixed with a script-specific prefix), but MUST declare such variables in the header of the file.
  17. MAY call other Vortex scripts (discouraged), but MUST source them rather than creating a sub-process. This is to allow passing environment variables down the call stack.
  18. SHOULD use note messages for informing about the script progress.
  19. MUST use variables in the form of ${VAR}.

Variables

Follow these guidelines when creating or updating Vortex variables.

  1. Local variables MUST be in lowercase, and global variables MUST be in uppercase.

  2. All Vortex variables MUST start with VORTEX_ to separate Vortex from third-party variables.

  3. Global variables MAY be re-used as-is across scripts. For instance, the WEBROOT variable is used in several scripts.

  4. Vortex action-specific script variables MUST be scoped within their own script. For instance, the VORTEX_PROVISION_OVERRIDE_DB variable in the provision.sh.

  5. Drupal-related variables SHOULD start with DRUPAL_ and SHOULD have a module name added as a second prefix. This is to separate Vortex, third-party services variables, and Drupal variables. For instance, to set a user for Drupal's Shield module configuration, use DRUPAL_SHIELD_USER.

  6. Variables SHOULD NOT be exported into the global scope unless absolutely necessary. Thus, values in .env SHOULD have default values set, but SHOULD be commented out to provide visibility and avoid exposure to the global scope.

Boilerplate script

Expand to see the boilerplate script
#!/usr/bin/env bash
##
# Action description that the script performs.
#
# More description and usage information with a last empty
# comment line.
#

set -eu
[ "${VORTEX_DEBUG-}" = "1" ] && set -x

# Example Vortex variable with a default value.
VORTEX_EXAMPLE_URL="${VORTEX_EXAMPLE_URL:-http://example.com}"

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

# @formatter:off
info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[INFO] %s\033[0m\n" "${1}" || printf "[INFO] %s\n" "${1}"; }
note() { printf " %s\n" "${1}"; }
pass() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s\033[0m\n" "${1}" || printf "[ OK ] %s\n" "${1}"; }
fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; }
# @formatter:on

info "Started Vortex operations."

[ -z "${VORTEX_EXAMPLE_URL}" ] && fail "Missing required value for VORTEX_EXAMPLE_URL" && exit 1
command -v curl >/dev/null || (fail "curl command is not available." && exit 1)

# Example of the script body.
curl -L -s -o /dev/null -w "%{http_code}" "${VORTEX_EXAMPLE_URL}" | grep -q '200\|403' && note "Requested example page"

pass "Finished Vortex operations."

Testing

Vortex is a project template, not a traditional application. A change to a script, configuration file, or workflow can affect every project that uses it. Because of this, Vortex uses a multi-layered testing approach to catch issues at different levels of integration before they reach consumers.

Unit testing

Bats is used to unit test the shell scripts in scripts/vortex/. Each script is tested in isolation with external commands (like drush, docker, composer) replaced by mocks. This allows us to verify that individual scripts handle environment variables, flags, and edge cases correctly without needing a running Drupal site or Docker containers.

The bats-helpers library provides a step-based testing approach with built-in mocking and assertions, making it straightforward to define expected inputs and outputs for each script.

Unit tests execute in seconds, providing fast feedback during development. There are 25+ test files covering deployment, database operations, notifications, provisioning, and other automation scripts.

info

Scripts are transitioning from Bash to PHP in Vortex 2.0, which will make the use of BATS obsolete. Track the progress in this issue.

End-to-end testing

PHPUnit is used for functional end-to-end testing that exercises the full site build pipeline in real Docker containers. These tests simulate what a consumer site developer would experience: building containers, importing databases, running Drupal operations, linting code, and deploying artifacts.

The template end-to-end tests cover several critical workflows:

  • Docker Compose - building the container stack, verifying environment variables, running linters, executing PHPUnit and Behat tests inside containers, and checking Solr integration.
  • Ahoy workflows - testing the full developer experience through Ahoy commands, including build, login, Drush operations, Composer, database import/export, and code quality checks.
  • Deployment - testing deployment pipelines for artifact and webhook-based deployment strategies.

The phpunit-helpers library provides test helpers, traits, and assertions purpose-built for running shell commands and validating their output within PHPUnit tests.

End-to-end tests take minutes to run because they operate on real containers and a real Drupal site, but they provide the highest confidence that everything works together correctly.

Example site

The DrevOps website is a real-world production site built using Vortex. It serves as the ultimate validation ground: if Vortex works correctly, the website should continue to build, test, and deploy without issues after each upstream update.

The website repository receives regular upstream updates from Vortex, including CI configuration, testing workflows, and infrastructure changes. This ensures that updates are validated against a real, long-lived codebase rather than just in test environments.

Running tests

Functional tests with PHPUnit

cd .vortex/tests

# Install Composer dependencies.
composer install

# Run all tests.
composer test

# Some tests require Composer and container registry tokens.
TEST_PACKAGE_TOKEN=<yourtoken> TEST_VORTEX_CONTAINER_REGISTRY_USER=<youruser> TEST_VORTEX_CONTAINER_REGISTRY_PASS=<yourpass> composer test

Functional tests rely on database fixtures - see the section below on updating test assets.

Unit tests with BATS

cd .vortex/tests

# Install npm dependencies.
npm install

# Run a single test.
bats .vortex/tests/bats/deploy.bats

Updating test assets

There are demo and test database dumps captured as files and container images. All flows are automated by .vortex/tests/update-test-assets, which prints every shell command it runs.

php .vortex/tests/update-test-assets [mode] [--tag <tag>]

Without arguments, runs all for a full refresh. Default tag is latest.

Modes

ModeWhat it doesFollow-up
demo-dumpBuilds the demo profile in-place; exports .data/db.demo.sql.Upload as db.demo.sql to the latest GitHub release.
demo-imageBuilds the demo profile in-place; pushes drevops/vortex-dev-mariadb-drupal-data-demo-11.x:<tag>.None.
test-dumpInstalls Vortex into /tmp/star-wars; builds; exports /tmp/star-wars/.data/db.test.sql.Upload as db_d11.test.sql to the latest GitHub release.
test-imageInstalls Vortex into /tmp/star-wars; builds; pushes drevops/vortex-dev-mariadb-drupal-data-test-11.x:<tag>.None.
destination-imagesTags and pushes the local demo image to the didi destination tags (vortex-dev-database-ii, vortex-dev-didi-database-fi).None.
allOptimised full refresh: builds the demo stack once for both demo modes and the test stack once for both test modes (2 stack builds instead of 4), then pushes images and tags destination images. Default when no mode is given.Upload both dump files as above.

Options

OptionDescription
--tag <tag>Image tag for image modes. Default: latest. Also accepts --tag=<tag>.

Requirements

  • docker, ahoy, curl, sed, php available on PATH.
  • A Docker Hub session with push permission to drevops/ for any image mode.

Caveats

  • Demo modes are destructive against the current repository: containers are reset and .data/db.sql is removed before the rebuild.
  • Test modes wipe and recreate /tmp/star-wars on each run.
  • destination-images expects drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest to already exist locally - run demo-image (or all) first.

Examples

# Full refresh of every asset at :latest (default).
php .vortex/tests/update-test-assets

# Full refresh, tagging images as :rc1.
php .vortex/tests/update-test-assets --tag rc1

# Refresh only the test container image at :latest.
php .vortex/tests/update-test-assets test-image

# Refresh only the test container image at :rc1.
php .vortex/tests/update-test-assets test-image --tag rc1