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
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

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.

Updating demo database dump file

Show instructions
  1. Run fresh build of Vortex locally:
rm .data/db.sql || true
VORTEX_PROVISION_TYPE=profile VORTEX_PROVISION_POST_OPERATIONS_SKIP=1 AHOY_CONFIRM_RESPONSE=1 ahoy build
  1. Update content and config:
ahoy cli

drush eval "Drupal::entityTypeManager()->getStorage('node')->create([
'type' => 'page',
'title' => 'Welcome to the demo site!',
'body' => [
'value' => '<p>This demo page is sourced from the Vortex database dump file to demonstrate database importing capabilities.</p>',
'format' => 'basic_html',
],
])->save();"

drush config:set system.site page.front "/node/1" -y
drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog"

exit

  1. Export DB:
ahoy export-db db.demo.sql
  1. Upload db.demo.sql to the latest release as an asset and name it db_d11.demo.sql.

Updating demo database container image

Show instructions
  1. Run fresh build of Vortex locally:
rm .data/db.sql || true
VORTEX_PROVISION_TYPE=profile VORTEX_PROVISION_POST_OPERATIONS_SKIP=1 AHOY_CONFIRM_RESPONSE=1 ahoy build
  1. Update content and config:
ahoy cli

drush eval "Drupal::entityTypeManager()->getStorage('node')->create([
'type' => 'page',
'title' => 'Welcome to the demo site!',
'body' => [
'value' => '<p>This demo page is sourced from the Vortex database container image to demonstrate database importing capabilities.</p>',
'format' => 'basic_html',
],
])->save();"

drush config:set system.site page.front "/node/1" -y
drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog"

exit

  1. Export DB:
ahoy export-db db.demo_image.sql

# Update the collation to avoid issues with MariaDB 10.5+:
sed -i '' 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' .data/db.demo_image.sql
  1. Seed the database container image:
curl -LO https://github.com/drevops/mariadb-drupal-data/releases/latest/download/seed.sh
chmod +x seed.sh
./seed.sh .data/db.demo_image.sql drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest

Updating test database dump file

Show instructions
  1. Run a fresh install of Vortex into a new directory and name the project Star Wars:
mkdir /tmp/star-wars
VORTEX_INSTALLER_TEMPLATE_REPO="$(pwd)" .vortex/installer/installer.php /tmp/star-wars --no-interaction
cd /tmp/star-wars
  1. Run fresh build of Vortex locally:
rm .data/db.sql || true
VORTEX_PROVISION_TYPE=profile AHOY_CONFIRM_RESPONSE=1 ahoy build
  1. Update content and config:
ahoy cli

drush eval "Drupal::entityTypeManager()->getStorage('node')->create([
'type' => 'page',
'title' => 'Welcome to the test site!',
'body' => [
'value' => '<p>This test page is sourced from the Vortex database dump file to demonstrate database importing capabilities.</p>',
'format' => 'basic_html',
],
])->save();"

drush config:set system.site page.front "/node/1" -y
drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog"

exit

  1. Export DB:
ahoy export-db db.test.sql
  1. Upload db.test.sql to the latest release as an asset and name it db_d11.test.sql.

Updating test database container image

Show instructions
  1. Run a fresh install of Vortex into a new directory and name the project Star Wars:
mkdir /tmp/star-wars
VORTEX_INSTALLER_TEMPLATE_REPO="$(pwd)" .vortex/installer/installer.php /tmp/star-wars --no-interaction
cd /tmp/star-wars
  1. Run fresh build of Vortex locally:
rm .data/db.sql || true
VORTEX_PROVISION_TYPE=profile AHOY_CONFIRM_RESPONSE=1 ahoy build
  1. Update content and config:
ahoy cli

drush eval "Drupal::entityTypeManager()->getStorage('node')->create([
'type' => 'page',
'title' => 'Welcome to the test site!',
'body' => [
'value' => '<p>This test page is sourced from the Vortex database container image to demonstrate database importing capabilities.</p>',
'format' => 'basic_html',
],
])->save();"

drush config:set system.site page.front "/node/1" -y
drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog"

exit

  1. Export DB:
ahoy export-db db.test_image.sql

# Update the collation to avoid issues with MariaDB 10.5+:
sed -i '' 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' .data/db.test_image.sql
  1. Seed the database container image:
curl -LO https://github.com/drevops/mariadb-drupal-data/releases/latest/download/seed.sh
chmod +x seed.sh
./seed.sh .data/db.test_image.sql drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest
  1. Update destination container images:
docker tag drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x:vortex-dev-database-ii
docker push drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x:vortex-dev-database-ii

docker tag drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x:vortex-dev-didi-database-fi
docker push drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x:vortex-dev-didi-database-fi