Skip to content

Reconciled notification variables in scripts. #2423

Reconciled notification variables in scripts.

Reconciled notification variables in scripts. #2423

# GitHub Actions configuration file.
#
# This configuration file uses a custom "container" executor to run the
# Docker stack to speed up the build process.
#;
#; Comments starting with '#;<' and '#;>' are internal Vortex comments
#; and will be removed during installation or update of Vortex.
name: Database, Build, Test and Deploy
on:
push:
# Pushes to the following branches will trigger the workflow.
branches:
- production
- main
- master
- develop
- release/**
- hotfix/**
- project/**
# Pushes of tags will also trigger the workflow.
tags:
- '*'
# Pull requests to the following branches will trigger the workflow.
pull_request:
types:
- opened
- synchronize
- reopened
branches:
- production
- main
- master
- develop
- release/**
- hotfix/**
- project/**
workflow_dispatch:
inputs:
enable_terminal:
type: boolean
description: 'Enable terminal session.'
required: false
default: false
#;< !PROVISION_TYPE_PROFILE
schedule:
- cron: '0 18 * * *'
#;> !PROVISION_TYPE_PROFILE
defaults:
run:
shell: bash
jobs:
#;< !PROVISION_TYPE_PROFILE
database:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' || !startsWith(github.head_ref, 'project/') }}
container:
# https://hub.docker.com/r/drevops/ci-runner
image: drevops/ci-runner:25.10.0@sha256:03f2722e141b4939b390abad9443ed96304a8ec7bda0b2eab9dac56f9e9b7b91
env:
PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
VORTEX_CONTAINER_REGISTRY_USER: ${{ secrets.VORTEX_CONTAINER_REGISTRY_USER }}
VORTEX_CONTAINER_REGISTRY_PASS: ${{ secrets.VORTEX_CONTAINER_REGISTRY_PASS }}
TZ: ${{ vars.TZ || 'UTC' }}
TERM: xterm-256color
VORTEX_SSH_DISABLE_STRICT_HOST_KEY_CHECKING: "1"
VORTEX_SSH_REMOVE_ALL_KEYS: "1"
VORTEX_DEBUG: ${{ vars.VORTEX_DEBUG }}
# How often to refresh the cache of the DB dump. Refer to `date` command.
VORTEX_CI_DB_CACHE_TIMESTAMP: +%Y%m%d
# Use previous database caches on this branch as a fallback if the above cache
# does not match (for example, the cache is available only from the previous
# day). If "no" is set, the cache will be rebuilt from scratch.
VORTEX_CI_DB_CACHE_FALLBACK: "yes"
# Which branch to use as a source of DB caches.
VORTEX_CI_DB_CACHE_BRANCH: "develop"
steps:
- name: Preserve $HOME set in the container
run: echo HOME=/root >> "$GITHUB_ENV" # https://github.com/actions/runner/issues/863
- name: Check out code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
# Do not keep SSH credentials after checkout to allow adding custom SSH keys.
persist-credentials: false
- name: Add SSH private key used to download database
if: ${{ env.VORTEX_DOWNLOAD_DB_SSH_PRIVATE_KEY != '' }}
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.VORTEX_DOWNLOAD_DB_SSH_PRIVATE_KEY }}
known_hosts: unnecessary
env:
VORTEX_DOWNLOAD_DB_SSH_PRIVATE_KEY: ${{ secrets.VORTEX_DOWNLOAD_DB_SSH_PRIVATE_KEY }}
- name: Process the codebase to run in CI
run: find . -name "docker-compose.yml" -print0 | xargs -0 -I {} sh -c "sed -i -e '/###/d' {} && sed -i -e 's/##//' {}"
- name: Adjust variables for a scheduled run
if: github.event_name == 'schedule'
run: |
echo "VORTEX_CI_DB_CACHE_FALLBACK=no" >> "$GITHUB_ENV"
echo "VORTEX_FRONTEND_BUILD_SKIP=1" >> "$GITHUB_ENV"
- name: Create cache keys files for database caching
run: |
echo "${VORTEX_CI_DB_CACHE_BRANCH}" | tee db_cache_branch
echo "${VORTEX_CI_DB_CACHE_FALLBACK/no/"${GITHUB_RUN_NUMBER}"}" | tee db_cache_fallback
date "${VORTEX_CI_DB_CACHE_TIMESTAMP}" | tee db_cache_timestamp
echo "yes" | tee db_cache_fallback_yes
# Restore DB cache based on the cache strategy set by the cache keys below.
# Change 'v1' to 'v2', 'v3' etc., commit and push to force cache reset.
# Lookup cache based on the default branch and a timestamp. Allows
# to use cache from the very first build on the day (sanitized database dump, for example).
- name: Restore DB cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .data
key: v25.10.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback') }}-${{ hashFiles('db_cache_timestamp') }}
# Fallback to caching by default branch name only. Allows to use
# cache from the branch build on the previous day.
restore-keys: |
v25.10.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback') }}-
- name: Download DB
run: |
VORTEX_DB_DOWNLOAD_SEMAPHORE=/tmp/download-db-success ./scripts/vortex/download-db.sh
echo "db_hash=${{ hashFiles('.data') }}" >> "$GITHUB_ENV"
timeout-minutes: 30
#;< DB_DOWNLOAD_SOURCE_ACQUIA
env:
VORTEX_ACQUIA_KEY: ${{ secrets.VORTEX_ACQUIA_KEY }}
VORTEX_ACQUIA_SECRET: ${{ secrets.VORTEX_ACQUIA_SECRET }}
#;> DB_DOWNLOAD_SOURCE_ACQUIA
- name: Export DB
run: |
if [ ! -f /tmp/download-db-success ]; then echo "==> Database download semaphore file is missing. DB export will not proceed."; exit 0; fi
./scripts/vortex/login-container-registry.sh
docker compose up --detach && sleep 15
docker compose exec cli mkdir -p .data && docker compose cp -L .data/db.sql cli:/app/.data/db.sql
docker compose exec cli bash -c "VORTEX_PROVISION_POST_OPERATIONS_SKIP=1 ./scripts/vortex/provision.sh"
./scripts/vortex/export-db.sh db.sql
timeout-minutes: 30
# Save cache per default branch and the timestamp.
# The cache will not be saved if it already exists.
# Note that the cache fallback flag is enabled for this case in order
# to save cache even if the fallback is not used when restoring it.
- name: Save DB cache
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
if: env.db_hash != hashFiles('.data')
with:
path: .data
key: v25.10.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }}
#;> !PROVISION_TYPE_PROFILE
build:
runs-on: ubuntu-latest
needs: database
#;< !PROVISION_TYPE_PROFILE
if: ${{ github.event_name != 'schedule' && (github.event_name == 'push' || !startsWith(github.head_ref, 'project/')) }}
#;> !PROVISION_TYPE_PROFILE
strategy:
# A matrix to run multiple jobs in parallel.
matrix:
instance: [0, 1]
fail-fast: false
container:
# https://hub.docker.com/r/drevops/ci-runner
image: drevops/ci-runner:25.10.0@sha256:03f2722e141b4939b390abad9443ed96304a8ec7bda0b2eab9dac56f9e9b7b91
env:
PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
VORTEX_CONTAINER_REGISTRY_USER: ${{ secrets.VORTEX_CONTAINER_REGISTRY_USER }}
VORTEX_CONTAINER_REGISTRY_PASS: ${{ secrets.VORTEX_CONTAINER_REGISTRY_PASS }}
TZ: ${{ vars.TZ || 'UTC' }}
TERM: xterm-256color
# Disable strict host key checking for SSH connections.
VORTEX_SSH_DISABLE_STRICT_HOST_KEY_CHECKING: "1"
VORTEX_SSH_REMOVE_ALL_KEYS: "1"
VORTEX_DEBUG: ${{ vars.VORTEX_DEBUG }}
#;< !PROVISION_TYPE_PROFILE
# How often to refresh the cache of the DB dump. Refer to `date` command.
VORTEX_CI_DB_CACHE_TIMESTAMP: +%Y%m%d
# Use previous database caches on this branch as a fallback if the above cache
# does not match (for example, the cache is available only from the previous
# day). If "no" is set, the cache will be rebuilt from scratch.
VORTEX_CI_DB_CACHE_FALLBACK: "yes"
# Which branch to use as a source of DB caches.
VORTEX_CI_DB_CACHE_BRANCH: "develop"
#;> !PROVISION_TYPE_PROFILE
steps:
- name: Preserve $HOME set in the container
run: echo HOME=/root >> "$GITHUB_ENV" # https://github.com/actions/runner/issues/863
- name: Check out code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Process the codebase to run in CI
run: find . -name "docker-compose.yml" -print0 | xargs -0 -I {} sh -c "sed -i -e '/###/d' {} && sed -i -e 's/##//' {}"
- name: Load environment variables from .env
run: t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && set +a && . "${t}" && env >> "$GITHUB_ENV"
- name: Validate Composer configuration
run: composer validate --strict
continue-on-error: ${{ vars.VORTEX_CI_COMPOSER_VALIDATE_IGNORE_FAILURE == '1' }}
#;< !PROVISION_TYPE_PROFILE
- name: Create cache keys files for database caching
run: |
echo "${VORTEX_CI_DB_CACHE_BRANCH}" | tee db_cache_branch
echo "yes" | tee db_cache_fallback_yes
date "${VORTEX_CI_DB_CACHE_TIMESTAMP}" | tee db_cache_timestamp
- name: Show cache key for database caching
run: echo 'v25.10.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }}'
# Restore DB cache based on the cache strategy set by the cache keys below.
# Change 'v1' to 'v2', 'v3' etc., commit and push to force cache reset.
# Lookup cache based on the default branch and a timestamp. Allows
# to use cache from the very first build on the day (sanitized database dump, for example).
- name: Restore DB cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .data
fail-on-cache-miss: true
# Use cached database from previous builds of this branch.
key: v25.10.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }}
restore-keys: |
v25.10.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-
#;> !PROVISION_TYPE_PROFILE
- name: Login to container registry
run: ./scripts/vortex/login-container-registry.sh
- name: Lint Dockerfiles with Hadolint
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
run: |
find .docker -name 'Dockerfile' -o -name '*.dockerfile' | while read -r file; do
echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint
done
continue-on-error: ${{ vars.VORTEX_CI_HADOLINT_IGNORE_FAILURE == '1' }}
- name: Lint Docker Compose files with DCLint
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
run: docker run --rm -v "${PWD}":/app zavoloklom/dclint:3.1.0 .
continue-on-error: ${{ vars.VORTEX_CI_DCLINT_IGNORE_FAILURE == '1' }}
- name: Build stack
run: docker compose up -d
- name: Export built codebase
if: ${{ matrix.instance == 0 && !startsWith(github.head_ref || github.ref_name, 'deps/') && contains(env.VORTEX_DEPLOY_TYPES, 'artifact') }}
run: |
mkdir -p "/tmp/workspace/code" "/tmp/artifacts"
docker compose cp -L cli:"/app/." "/tmp/workspace/code"
tar -C /tmp/workspace -cpf /tmp/artifacts/code.tar code
du -sh "/tmp/artifacts"
- name: Install development dependencies
run: |
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- name: Validate Composer configuration is normalized
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
run: docker compose exec -T cli composer normalize --dry-run
continue-on-error: ${{ vars.VORTEX_CI_COMPOSER_NORMALIZE_IGNORE_FAILURE == '1' }}
#;< TOOL_PHPCS
- name: Lint code with PHPCS
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
run: docker compose exec -T cli vendor/bin/phpcs
continue-on-error: ${{ vars.VORTEX_CI_PHPCS_IGNORE_FAILURE == '1' }}
#;> TOOL_PHPCS
#;< TOOL_PHPSTAN
- name: Lint code with PHPStan
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
run: docker compose exec -T cli vendor/bin/phpstan
continue-on-error: ${{ vars.VORTEX_CI_PHPSTAN_IGNORE_FAILURE == '1' }}
#;> TOOL_PHPSTAN
#;< TOOL_RECTOR
- name: Lint code with Rector
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
run: docker compose exec -T cli vendor/bin/rector --clear-cache --dry-run
continue-on-error: ${{ vars.VORTEX_CI_RECTOR_IGNORE_FAILURE == '1' }}
#;> TOOL_RECTOR
#;< TOOL_PHPMD
- name: Lint code with PHPMD
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
run: docker compose exec -T cli vendor/bin/phpmd . text phpmd.xml
continue-on-error: ${{ vars.VORTEX_CI_PHPMD_IGNORE_FAILURE == '1' }}
#;> TOOL_PHPMD
- name: Lint code with Twig CS Fixer
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
run: docker compose exec -T cli vendor/bin/twig-cs-fixer
continue-on-error: ${{ vars.VORTEX_CI_TWIG_CS_FIXER_IGNORE_FAILURE == '1' }}
#;< TOOL_BEHAT
- name: Lint code with Gherkin Lint
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
run: docker compose exec -T cli vendor/bin/gherkinlint lint tests/behat/features
continue-on-error: ${{ vars.VORTEX_CI_GHERKIN_LINT_IGNORE_FAILURE == '1' }}
#;> TOOL_BEHAT
- name: Lint module code with NodeJS linters
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
run: docker compose exec -T cli bash -c "yarn run lint"
continue-on-error: ${{ vars.VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE == '1' }}
#;< DRUPAL_THEME
- name: Lint theme code with NodeJS linters
if: ${{ (matrix.instance == 0 || strategy.job-total == 1) && vars.VORTEX_FRONTEND_BUILD_SKIP != '1' }}
run: docker compose exec -T cli bash -c "yarn --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} run lint"
continue-on-error: ${{ vars.VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE == '1' }}
#;> DRUPAL_THEME
- name: Provision site
run: |
if [ -f .data/db.sql ]; then
docker compose exec cli mkdir -p .data
docker compose cp -L .data/db.sql cli:/app/.data/db.sql
fi
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
timeout-minutes: 30
#;< TOOL_PHPUNIT
- name: Test with PHPUnit
run: docker compose exec -T cli vendor/bin/phpunit
continue-on-error: ${{ vars.VORTEX_CI_PHPUNIT_IGNORE_FAILURE == '1' }}
#;> TOOL_PHPUNIT
#;< TOOL_BEHAT
- name: Test with Behat
run: |
# shellcheck disable=SC2170
if [ ${{ strategy.job-total }} -gt 1 ]; then export VORTEX_CI_BEHAT_PROFILE="${VORTEX_CI_BEHAT_PROFILE:-p${{ strategy.job-index }}}"; fi
echo "Running with ${VORTEX_CI_BEHAT_PROFILE:-default} profile"
docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" || \
docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --rerun --profile="${VORTEX_CI_BEHAT_PROFILE:-default}"
env:
VORTEX_CI_BEHAT_PROFILE: ${{ vars.VORTEX_CI_BEHAT_PROFILE }}
continue-on-error: ${{ vars.VORTEX_CI_BEHAT_IGNORE_FAILURE == '1' }}
timeout-minutes: 30
#;> TOOL_BEHAT
- name: Process test logs and artifacts
if: always()
run: |
mkdir -p ".logs"
if docker compose ps --services --filter "status=running" | grep -q cli && docker compose exec cli test -d /app/.logs; then
docker compose cp cli:/app/.logs/. ".logs/"
fi
- name: Upload test artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
if: always()
with:
name: test-artifacts-${{ matrix.instance }}
path: .logs
include-hidden-files: true
if-no-files-found: error
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
if: ${{ env.CODECOV_TOKEN != '' }}
with:
directory: .logs/coverage
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Upload exported codebase as an artifact
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
if: ${{ matrix.instance == 0 && !startsWith(github.head_ref || github.ref_name, 'deps/') && contains(env.VORTEX_DEPLOY_TYPES, 'artifact') }}
with:
name: code-artifact
path: "/tmp/artifacts/code.tar"
include-hidden-files: true
if-no-files-found: error
retention-days: 1
- name: Setup tmate session
if: ${{ !cancelled() && github.event.inputs.enable_terminal }}
uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101 # v3
timeout-minutes: 120 # Cancel the action after 15 minutes, regardless of whether a connection has been established.
with:
detached: true
#;< DEPLOYMENT
deploy:
runs-on: ubuntu-latest
needs: build
#;< !PROVISION_TYPE_PROFILE
if: ${{ github.event_name != 'schedule' && !startsWith(github.head_ref || github.ref_name, 'deps/') && (github.event_name == 'push' || !startsWith(github.head_ref, 'project/')) }}
#;> !PROVISION_TYPE_PROFILE
container:
# https://hub.docker.com/r/drevops/ci-runner
image: drevops/ci-runner:25.10.0@sha256:03f2722e141b4939b390abad9443ed96304a8ec7bda0b2eab9dac56f9e9b7b91
env:
TZ: ${{ vars.TZ || 'UTC' }}
TERM: xterm-256color
VORTEX_SSH_DISABLE_STRICT_HOST_KEY_CHECKING: "1"
VORTEX_DEBUG: ${{ vars.VORTEX_DEBUG }}
steps:
- name: Preserve $HOME set in the container
run: echo HOME=/root >> "$GITHUB_ENV" # https://github.com/actions/runner/issues/863
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
# Fetch all history for git repository.
fetch-depth: 0
# Do not persist credentials after checkout
# to allow using the custom credentials to push to a remote repo.
persist-credentials: false
ref: ${{ github.head_ref || github.ref_name }}
- name: Load environment variables from .env
run: t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && set +a && . "${t}" && env >> "$GITHUB_ENV"
- name: Download exported codebase as an artifact
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
if: ${{ contains(env.VORTEX_DEPLOY_TYPES, 'artifact') }}
with:
name: code-artifact
path: "/tmp/artifacts"
- name: Unpack downloaded exported codebase
if: ${{ contains(env.VORTEX_DEPLOY_TYPES, 'artifact') }}
run: |
mkdir -p /tmp/workspace
tar -xpf /tmp/artifacts/code.tar -C /tmp/workspace
- name: Add SSH private key to the runner
if: ${{ env.VORTEX_DEPLOY_SSH_PRIVATE_KEY != '' }}
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.VORTEX_DEPLOY_SSH_PRIVATE_KEY }}
known_hosts: unnecessary
env:
VORTEX_DEPLOY_SSH_PRIVATE_KEY: ${{ secrets.VORTEX_DEPLOY_SSH_PRIVATE_KEY }}
- name: Deploy
run: ./scripts/vortex/deploy.sh
env:
VORTEX_DEPLOY_MODE: ${{ startsWith(github.ref, 'refs/tags/') && 'tag' || 'branch' }}
# Get branch for PR from 'head_ref' or for branch from 'ref_name'.
VORTEX_DEPLOY_BRANCH: ${{ github.head_ref || github.ref_name }}
VORTEX_DEPLOY_PR: ${{ github.event.number }}
VORTEX_DEPLOY_PR_HEAD: ${{ github.event.pull_request.head.sha }}
VORTEX_DEPLOY_ARTIFACT_SRC: /tmp/workspace/code
VORTEX_DEPLOY_ARTIFACT_ROOT: ${{ github.workspace }}
VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE: ${{ vars.VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE }}
VORTEX_DEPLOY_ARTIFACT_GIT_USER_EMAIL: ${{ vars.VORTEX_DEPLOY_ARTIFACT_GIT_USER_EMAIL }}
VORTEX_DEPLOY_ARTIFACT_GIT_USER_NAME: ${{ vars.VORTEX_DEPLOY_ARTIFACT_GIT_USER_NAME }}
VORTEX_DEPLOY_WEBHOOK_URL: ${{ vars.VORTEX_DEPLOY_WEBHOOK_URL }}
VORTEX_DEPLOY_SKIP: ${{ vars.VORTEX_DEPLOY_SKIP }}
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 }}
timeout-minutes: 30
#;> DEPLOYMENT