From c990188b00ec2e605c30b934cabf9c5c7ccc29ec Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 20 Feb 2026 11:49:19 +1100 Subject: [PATCH] Updated to Vortex 1.36.0 --- .ahoy.yml | 9 +- .docker/clamav.dockerfile | 2 +- .docker/cli.dockerfile | 10 +- .env | 25 +- .env.local.example | 9 +- .github/workflows/build-test-deploy.yml | 26 +- .lagoon.yml | 2 +- AGENTS.md | 91 ++ CLAUDE.md | 894 +----------------- README.md | 4 +- behat.yml | 13 +- composer.json | 14 +- composer.lock | 314 +++--- docs/README.md | 16 +- docs/ci.md | 31 +- docs/deployment.md | 30 +- docs/faqs.md | 168 +--- docs/releasing.md | 31 +- docs/testing.md | 227 ++++- drush/php-ini/drush.ini | 9 + package.json | 2 +- phpunit.xml | 48 +- renovate.json | 64 +- scripts/vortex/deploy-artifact.sh | 6 +- scripts/vortex/deploy-lagoon.sh | 100 +- scripts/vortex/download-db-acquia.sh | 104 +- .../vortex/download-db-container-registry.sh | 48 +- scripts/vortex/download-db-ftp.sh | 32 +- scripts/vortex/download-db-lagoon.sh | 60 +- scripts/vortex/download-db-s3.sh | 129 +++ scripts/vortex/download-db-url.sh | 36 +- scripts/vortex/download-db.sh | 46 +- scripts/vortex/export-db-file.sh | 6 +- scripts/vortex/export-db-image.sh | 30 +- scripts/vortex/export-db.sh | 8 +- scripts/vortex/login-container-registry.sh | 22 +- scripts/vortex/login.sh | 4 +- scripts/vortex/logout.sh | 4 +- scripts/vortex/notify-github.sh | 7 +- scripts/vortex/notify-jira.sh | 2 +- scripts/vortex/notify-newrelic.sh | 2 +- scripts/vortex/notify-slack.sh | 2 +- scripts/vortex/provision.sh | 132 ++- scripts/vortex/task-copy-db-acquia.sh | 20 +- scripts/vortex/task-copy-files-acquia.sh | 20 +- scripts/vortex/task-custom-lagoon.sh | 60 +- scripts/vortex/task-purge-cache-acquia.sh | 20 +- scripts/vortex/upload-db-s3.sh | 138 +++ tests/behat/bootstrap/FeatureContext.php | 2 +- .../Drupal/EnvironmentSettingsTest.php | 18 - tests/phpunit/Drupal/SettingsTestCase.php | 4 +- .../phpunit/Drupal/SwitchableSettingsTest.php | 84 ++ tests/phpunit/bootstrap.php | 28 + .../includes/modules/settings.shield.php | 10 +- yarn.lock | 867 ++++++++--------- 55 files changed, 1966 insertions(+), 2124 deletions(-) create mode 100644 AGENTS.md create mode 100644 drush/php-ini/drush.ini create mode 100755 scripts/vortex/download-db-s3.sh create mode 100755 scripts/vortex/upload-db-s3.sh create mode 100644 tests/phpunit/bootstrap.php diff --git a/.ahoy.yml b/.ahoy.yml index e131710d..02af3abe 100644 --- a/.ahoy.yml +++ b/.ahoy.yml @@ -50,7 +50,7 @@ commands: up: usage: Build and start containers. cmd: | - docker compose up -d "$@" + docker compose up --detach "$@" if docker compose logs | grep -q "\[Error\]"; then exit 1; fi down: @@ -115,7 +115,7 @@ commands: usage: Download database. Run with "--fresh" option to force fresh database backup. aliases: [fetch-db] cmd: | - case " $* " in *" --fresh "*) export VORTEX_DB_DOWNLOAD_FRESH=1;; esac + case " $* " in *" --fresh "*) export VORTEX_DOWNLOAD_DB_FRESH=1;; esac ./scripts/vortex/download-db.sh reload-db: @@ -234,6 +234,11 @@ commands: usage: Run PHPUnit functional tests. cmd: ahoy cli vendor/bin/phpunit --testsuite=functional "$@" + test-functional-javascript: + aliases: [test-js] + usage: Run PHPUnit functional JavaScript tests. + cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@" + test-bdd: usage: Run BDD tests. aliases: [test-behat] diff --git a/.docker/clamav.dockerfile b/.docker/clamav.dockerfile index f3f5e8da..4012b921 100644 --- a/.docker/clamav.dockerfile +++ b/.docker/clamav.dockerfile @@ -12,7 +12,7 @@ FROM uselagoon/commons:26.1.0 AS commons -FROM clamav/clamav-debian:1.5.1-26 +FROM clamav/clamav-debian:1.5.1-28 COPY --from=commons /lagoon /lagoon COPY --from=commons /bin/fix-permissions /bin/ep /bin/docker-sleep /bin/wait-for /bin/ diff --git a/.docker/cli.dockerfile b/.docker/cli.dockerfile index fc860e27..17963b06 100644 --- a/.docker/cli.dockerfile +++ b/.docker/cli.dockerfile @@ -39,10 +39,12 @@ ARG VORTEX_FRONTEND_BUILD_SKIP="0" ENV VORTEX_FRONTEND_BUILD_SKIP=${VORTEX_FRONTEND_BUILD_SKIP} ENV COMPOSER_ALLOW_SUPERUSER=1 \ - COMPOSER_CACHE_DIR=/tmp/.composer/cache \ - SIMPLETEST_DB=mysql://drupal:drupal@database/drupal \ - SIMPLETEST_BASE_URL=http://nginx:8080 \ - SYMFONY_DEPRECATIONS_HELPER=disabled + COMPOSER_CACHE_DIR=/tmp/.composer/cache + +# Allow custom PHP runtime configuration for Drush CLI commands. +# The leading colon appends to the default scan directories. +# @see https://github.com/drevops/vortex/issues/1913 +ENV PHP_INI_SCAN_DIR="${PHP_INI_SCAN_DIR}:/app/drush/php-ini" # Starting from this line, Docker adds the result of each command as a # separate layer. These layers are cached and reused when the project is diff --git a/.env b/.env index 37e753c0..253814ba 100644 --- a/.env +++ b/.env @@ -15,7 +15,7 @@ # To customize variables locally, copy `.env.local.example` to `.env.local`, # and add your custom values there. # -# @see https://www.vortextemplate.com/docs/workflows/variables +# @see https://www.vortextemplate.com/docs/development/variables ################################################################################ # GENERAL # @@ -103,6 +103,19 @@ VORTEX_PROVISION_TYPE=database # Set this to 1 in .env.local to override when developing locally. VORTEX_PROVISION_OVERRIDE_DB=0 +# Fallback to profile installation if the database dump is not available. +# +# When enabled and the provision type is set to "database", the site will be +# installed from the profile if the database dump file or container image +# is not available. +VORTEX_PROVISION_FALLBACK_TO_PROFILE=0 + +# Verify that configuration was not changed by database updates. +# If enabled and config files are present, the provision will fail if +# database update hooks modify active configuration, preventing +# drush config:import from silently overwriting those changes. +VORTEX_PROVISION_VERIFY_CONFIG_UNCHANGED_AFTER_UPDATE=0 + # Skip database sanitization. # # Database sanitization is enabled by default in all non-production @@ -154,14 +167,14 @@ VORTEX_DB_DIR=./.data VORTEX_DB_FILE=db.sql # Database download source. -VORTEX_DB_DOWNLOAD_SOURCE=lagoon +VORTEX_DOWNLOAD_DB_SOURCE=lagoon # Environment to download the database from. # # Applies to hosting environments. # Note that depending on the hosting provider, this variable may represent # a branch name or an environment name. -VORTEX_DB_DOWNLOAD_ENVIRONMENT=main +VORTEX_DOWNLOAD_DB_ENVIRONMENT=main ################################################################################ # RELEASE VERSIONING # @@ -170,7 +183,7 @@ VORTEX_DB_DOWNLOAD_ENVIRONMENT=main # Versioning scheme used for releases. # # Can be one of: calver, semver, other -# @see https://www.vortextemplate.com/docs/workflows/releasing +# @see https://www.vortextemplate.com/docs/releasing VORTEX_RELEASE_VERSION_SCHEME=calver ################################################################################ @@ -178,7 +191,7 @@ VORTEX_RELEASE_VERSION_SCHEME=calver ################################################################################ # Deployment occurs when tests pass in the CI environment. -# @see https://www.vortextemplate.com/docs/workflows/deployment +# @see https://www.vortextemplate.com/docs/deployment VORTEX_DEPLOY_TYPES=lagoon ################################################################################ @@ -186,7 +199,7 @@ VORTEX_DEPLOY_TYPES=lagoon ################################################################################ # Notificaions are sent accross multiple channels before and after deployment. -# @see https://www.vortextemplate.com/docs/workflows/notifications +# @see https://www.vortextemplate.com/docs/deployment/notifications # The channels of the notifications. # diff --git a/.env.local.example b/.env.local.example index d9e7c187..c09b5ed2 100644 --- a/.env.local.example +++ b/.env.local.example @@ -6,7 +6,7 @@ # # The .env.local file is excluded via .gitignore and will not be committed. # -# @see https://www.vortextemplate.com/docs/workflows/variables +# @see https://www.vortextemplate.com/docs/development/variables # Local development URL. # Override only if you need to use a different URL than the default. @@ -31,12 +31,9 @@ VORTEX_PROVISION_OVERRIDE_DB=1 PACKAGE_TOKEN= # Always override existing downloaded DB dump. -VORTEX_DB_DOWNLOAD_FORCE=1 +VORTEX_DOWNLOAD_DB_FORCE=1 # Database dump file sourced from Lagoon. # SSH file used to download the database dump from Lagoon. Defaults to "${HOME}/.ssh/id_rsa}". -# VORTEX_DB_DOWNLOAD_SSH_FILE= - -# OpenAI API key for AI-related features. -DRUPAL_AI_PROVIDER_OPENAI_API_KEY= +# VORTEX_DOWNLOAD_DB_SSH_FILE= diff --git a/.github/workflows/build-test-deploy.yml b/.github/workflows/build-test-deploy.yml index 15edc896..ee0df4ab 100644 --- a/.github/workflows/build-test-deploy.yml +++ b/.github/workflows/build-test-deploy.yml @@ -54,7 +54,7 @@ jobs: container: # https://hub.docker.com/r/drevops/ci-runner - image: drevops/ci-runner:26.1.0@sha256:1fd1a2bc6311ae1c8b726fa2326dc509f6c8918b7d394bceb10d3aada1c7afcb + image: drevops/ci-runner:26.2.0@sha256:fe1561c2984a1023e84eebe6461056b0b55afedbb2512e6c2c7f19aca6beb398 env: PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }} VORTEX_CONTAINER_REGISTRY_USER: ${{ secrets.VORTEX_CONTAINER_REGISTRY_USER }} @@ -116,15 +116,15 @@ jobs: uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: .data - key: v26.1.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback') }}-${{ hashFiles('db_cache_timestamp') }} + key: v26.2.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: | - v26.1.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback') }}- + v26.2.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 + VORTEX_DOWNLOAD_DB_SEMAPHORE=/tmp/download-db-success ./scripts/vortex/download-db.sh echo "db_hash=${{ hashFiles('.data') }}" >> "$GITHUB_ENV" timeout-minutes: 30 @@ -147,7 +147,7 @@ jobs: if: env.db_hash != hashFiles('.data') with: path: .data - key: v26.1.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }} + key: v26.2.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }} build: runs-on: ubuntu-latest @@ -162,7 +162,7 @@ jobs: container: # https://hub.docker.com/r/drevops/ci-runner - image: drevops/ci-runner:26.1.0@sha256:1fd1a2bc6311ae1c8b726fa2326dc509f6c8918b7d394bceb10d3aada1c7afcb + image: drevops/ci-runner:26.2.0@sha256:fe1561c2984a1023e84eebe6461056b0b55afedbb2512e6c2c7f19aca6beb398 env: PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }} VORTEX_CONTAINER_REGISTRY_USER: ${{ secrets.VORTEX_CONTAINER_REGISTRY_USER }} @@ -206,7 +206,7 @@ jobs: date "${VORTEX_CI_DB_CACHE_TIMESTAMP}" | tee db_cache_timestamp - name: Show cache key for database caching - run: echo 'v26.1.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }}' + run: echo 'v26.2.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. @@ -218,9 +218,9 @@ jobs: path: .data fail-on-cache-miss: true # Use cached database from previous builds of this branch. - key: v26.1.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }} + key: v26.2.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }} restore-keys: | - v26.1.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}- + v26.2.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}- - name: Login to container registry run: ./scripts/vortex/login-container-registry.sh @@ -239,7 +239,7 @@ jobs: continue-on-error: ${{ vars.VORTEX_CI_DCLINT_IGNORE_FAILURE == '1' }} - name: Build stack - run: docker compose up -d + run: docker compose up --detach && docker builder prune --all --force - name: Export built codebase if: ${{ matrix.instance == 0 && !startsWith(github.head_ref || github.ref_name, 'deps/') && contains(env.VORTEX_DEPLOY_TYPES, 'artifact') }} @@ -345,9 +345,9 @@ jobs: - name: Check code coverage threshold if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} run: | - RATE=$(grep -o 'line-rate="[0-9.]*"' .logs/coverage/phpunit/cobertura.xml | head -1 | tr -cd '0-9.') + RATE=$(grep -om1 'line-rate="[0-9.]*"' .logs/coverage/phpunit/cobertura.xml | tr -cd '0-9.') PERCENT=$(awk "BEGIN {printf \"%.2f\", $RATE*100}") - echo "Coverage: $PERCENT% (threshold: $VORTEX_CI_CODE_COVERAGE_THRESHOLD%)" + echo "Coverage: $PERCENT% (threshold: $VORTEX_CI_CODE_COVERAGE_THRESHOLD%)" | tee -a "$GITHUB_STEP_SUMMARY" if [ "${PERCENT//./}" -lt "$((VORTEX_CI_CODE_COVERAGE_THRESHOLD*100))" ]; then echo "FAIL: coverage too low" exit 1 @@ -400,7 +400,7 @@ jobs: container: # https://hub.docker.com/r/drevops/ci-runner - image: drevops/ci-runner:26.1.0@sha256:1fd1a2bc6311ae1c8b726fa2326dc509f6c8918b7d394bceb10d3aada1c7afcb + image: drevops/ci-runner:26.2.0@sha256:fe1561c2984a1023e84eebe6461056b0b55afedbb2512e6c2c7f19aca6beb398 env: TZ: ${{ vars.TZ || 'UTC' }} TERM: xterm-256color diff --git a/.lagoon.yml b/.lagoon.yml index ede0bff8..95d16738 100644 --- a/.lagoon.yml +++ b/.lagoon.yml @@ -50,7 +50,7 @@ tasks: command: | if [ "$LAGOON_ENVIRONMENT_TYPE" != "production" ] && [ "$LAGOON_GIT_BRANCH" != "${VORTEX_LAGOON_PRODUCTION_BRANCH:-main}" ]; then # No need to load SSH file to access production DB as Lagoon has SSH agent keys. - export VORTEX_DB_DOWNLOAD_SSH_FILE=false + export VORTEX_DOWNLOAD_DB_SSH_FILE=false export VORTEX_DB_DIR=/tmp/data rm -Rf $VORTEX_DB_DIR || true ./scripts/vortex/download-db.sh diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..4e3fd9ff --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,91 @@ +# DrevOps Website - Development Guide + +## Daily Development Tasks + +```bash +# Environment +ahoy up # Start containers +ahoy down # Stop containers +ahoy info # Show URLs and status +ahoy login # Get admin login URL + +# Build & Database +ahoy download-db # Download fresh database from remote +ahoy build # Complete site rebuild +ahoy provision # Re-provision (import DB + apply config) +ahoy import-db # Import database from file without applying config +ahoy export-db # Export current local database + +# Drush commands +ahoy drush cr # Clear cache +ahoy drush updb # Run database updates +ahoy drush cex # Export configuration to code +ahoy drush cim # Import configuration from code +ahoy drush uli # Get one-time login link +ahoy drush status # Check site status + +# Composer +ahoy composer install +ahoy composer require drupal/[module_name] + +# Code quality +ahoy lint # Check code style +ahoy lint-fix # Auto-fix code style + +# PHPUnit testing +ahoy test # Run PHPUnit tests +ahoy test-unit # Run PHPUnit Unit tests +ahoy test-kernel # Run PHPUnit Kernel tests +ahoy test-functional # Run PHPUnit Functional tests +ahoy test -- --filter=TestClassName # Run specific PHPUnit test class + +# Behat testing +ahoy test-bdd # Run Behat tests +ahoy test-bdd -- --tags=@tagname # Run Behat tests with specific tag +``` + +## Before Starting Any Task + +1. **Check cached docs first.** Before investigating any topic, check `.data/ai-artifacts/docs-[topic].md` for existing cached documentation. Do not search the codebase or fetch from the web if a cached doc already exists. +2. **Check project docs.** Before making implementation decisions, check the relevant file in `docs/` for project-specific conventions. +3. **Fetch and cache if missing.** If no cached doc exists for the topic, fetch from https://www.vortextemplate.com/docs and save to `.data/ai-artifacts/docs-[topic].md` (see [Documentation](#documentation) for format). + +## Critical Rules + +- **Never modify** `scripts/vortex/` - use `scripts/custom/` for your scripts +- **Never use** `ahoy drush php:eval` - use `ahoy drush php:script` instead +- **Always export config** after admin UI changes: `ahoy drush cex` + +## Key Directories + +- `web/modules/custom/` - Custom modules +- `web/themes/custom/` - Custom themes +- `config/default/` - Drupal configuration +- `scripts/custom/` - Project scripts +- `patches/` - Module patches + +## Documentation + +This project uses two documentation sources: + +### Project-specific documentation (`docs/`) + +The `docs/` directory contains **what** applies to this project: + +- `docs/testing.md` - Testing conventions and agreements +- `docs/ci.md` - CI provider and configuration +- `docs/deployment.md` - Hosting provider and deployment rules +- `docs/releasing.md` - Version scheme and release process +- `docs/faqs.md` - Project-specific FAQs + +**Always check these files first** to understand project-specific decisions. + +### Vortex documentation (vortextemplate.com) + +For **how** to perform operations, fetch from https://www.vortextemplate.com/docs. + +Use the sitemap to discover available pages: https://www.vortextemplate.com/sitemap.xml + +**Caching:** Save fetched docs to `.data/ai-artifacts/docs-[topic].md` with header +``. +Re-fetch if user reports docs are outdated. diff --git a/CLAUDE.md b/CLAUDE.md index 21e4783f..0555fa32 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,893 +1,3 @@ -# Vortex Drupal Project - Development Guide +# AI Agent Configuration - - -## Project Overview - -This is a **production-ready Drupal project** built with **Vortex** - a -comprehensive Drupal project template by DrevOps that provides: - -- ๐Ÿณ **Docker-based development environment** -- ๐Ÿ”„ **Automated CI/CD deployment workflows** -- ๐Ÿงช **Comprehensive testing framework** -- โš™๏ธ **Configuration management** (exportable configs) -- ๐Ÿš€ **Production hosting integration** - -## Quick Start Commands - -```bash -# STEP 1: Build the site locally (first time setup) -ahoy build - -# STEP 2: Start development environment -ahoy up - -# STEP 3: Get site information and URLs -ahoy info - -# STEP 4: Get admin login link -ahoy login -``` - -## Core Development Workflow - -### 1. Environment Management - -```bash -# Start Docker containers (daily workflow) -ahoy up - -# Stop Docker containers (end of day) -ahoy down - -# Restart all containers (troubleshooting) -ahoy restart - -# Show project URLs, container status, database info -ahoy info -``` - -### 2. Site Building & Database - -```bash -# Complete site rebuild (nuclear option) -ahoy build - -# Re-provision site (install/import fresh DB) -ahoy provision - -# Reset to clean state (removes local changes) -ahoy reset -``` - -```bash -# Download fresh database from remote source -ahoy download-db - -# Export current local database -ahoy export-db - -# Import database from file -ahoy import-db [path/to/dump.sql] -``` - -### 3. Daily Development Tasks - -```bash -# Run Drush commands (Drupal CLI) -ahoy drush [command] -# Examples: -ahoy drush status -ahoy drush cr # Clear cache -ahoy drush uli # Get login link - -# Run Composer commands (PHP dependencies) -ahoy composer [command] -# Examples: -ahoy composer install -ahoy composer require drupal/webform -``` - -## Code Quality & Testing - -### Linting (Code Standards) - -```bash -# Check code style issues (PHP, JS, CSS) -ahoy lint - -# Automatically fix code style issues -ahoy lint-fix -``` - -### Testing Framework - -```bash -# Run PHPUnit tests (unit/integration tests) -ahoy test-unit -``` - -```bash -# Run Behat tests (behavioral/BDD tests) -ahoy test-bdd - -# Run specific Behat feature -ahoy test-bdd tests/behat/features/homepage.feature -``` - -## Configuration Management (Critical for Drupal) - -### Understanding Config Management - -- **Structure changes** (content types, fields, views) = Configuration (exported - to code) -- **Content data** (nodes, users, media) = Database (not exported) - -### Export Configuration (After making admin changes) - -```bash -# Export ALL configuration changes to code -ahoy drush config:export -# Short version: -ahoy drush cex - -# Export with diff preview -ahoy drush config:export --diff -``` - -### Import Configuration (Deploy config changes) - -```bash -# Import configuration from code -ahoy drush config:import -# Short version: -ahoy drush cim - -# Import from specific environment -ahoy drush config:import --source=../config/stage -``` - -### Typical Config Workflow - -1. Make changes in Drupal admin UI -2. Run `ahoy drush cex` to export to code -3. Commit the config files to git -4. Deploy code and run `ahoy drush cim` on target environment - -## Project Structure (Critical Understanding) - -```text -your-project/ -โ”œโ”€โ”€ .ahoy.yml # Ahoy task definitions -โ”œโ”€โ”€ .env # Environment variables (local) -โ”œโ”€โ”€ docker-compose.yml # Local development containers -โ”œโ”€โ”€ composer.json # PHP dependencies -โ”‚ -โ”œโ”€โ”€ config/ # Drupal configuration (version controlled) -โ”‚ โ”œโ”€โ”€ default/ # Base configuration (all environments) -โ”‚ โ”œโ”€โ”€ dev/ # Development-specific overrides -โ”‚ โ”œโ”€โ”€ stage/ # Staging-specific overrides -โ”‚ โ””โ”€โ”€ ci/ # CI-specific overrides -โ”‚ -โ”œโ”€โ”€ web/ # Drupal webroot (document root) -โ”‚ โ”œโ”€โ”€ modules/custom/ # Your custom modules -โ”‚ โ”œโ”€โ”€ themes/custom/ # Your custom themes -โ”‚ โ”œโ”€โ”€ sites/default/ # Drupal site settings -โ”‚ โ””โ”€โ”€ index.php # Drupal entry point -โ”‚ -โ”œโ”€โ”€ tests/ -โ”‚ โ”œโ”€โ”€ behat/ # Behavioral tests (user scenarios) -โ”‚ โ”‚ โ”œโ”€โ”€ features/ # Test scenarios (.feature files) -โ”‚ โ”‚ โ””โ”€โ”€ behat.yml # Behat configuration -โ”‚ โ””โ”€โ”€ phpunit/ # Unit/integration tests -โ”‚ -โ””โ”€โ”€ scripts/ - โ”œโ”€โ”€ vortex/ # Core Vortex scripts (don't modify) - โ””โ”€โ”€ custom/ # Project-specific scripts -``` - -## Custom Code Development - -### Creating Custom Modules - -```bash -# Generate custom module scaffold -ahoy drush generate:module - -# Location: web/modules/custom/[module_name]/ -# Enable module: -ahoy drush pm:install [module_name] -``` - -### Theme Development - -```bash -# Navigate to custom theme -cd web/themes/custom/[theme_name] - -# Install theme dependencies -yarn install - -# Build theme assets (CSS/JS) -yarn run build - -# Watch for changes during development -yarn run watch - -# Build for production -yarn run build:prod -``` - -## PHP Script Execution (IMPORTANT) - -### โœ… Correct Way: Use PHP Scripts - -```bash -# Run PHP script with full Drupal bootstrap -ahoy drush php:script script_name - -# List available scripts -ahoy drush php:script - -# Run with custom script path -ahoy drush php:script script_name --script-path=scripts/custom - -# Pass arguments to script (note the -- separator) -ahoy drush php:script -- script_name --arg1=value1 --arg2=value2 -``` - -### โŒ NEVER Do This - -```bash -# DANGEROUS - Never evaluate PHP directly! -ahoy drush php:eval "dangerous_code_here" -``` - -### Creating PHP Scripts - -Create scripts in `scripts/custom/` directory: - -```php -getStorage('node') - ->loadByProperties(['type' => 'page']); - -foreach ($nodes as $node) { - print $node->getTitle(); -} -``` - -## Service Integrations - -### Solr Search Service - -```bash -# Check Solr search status -ahoy drush search-api:status - -# Index all content to Solr -ahoy drush search-api:index - -# Clear and rebuild Solr index -ahoy drush search-api:clear -ahoy drush search-api:index - -# Check Solr server connection -ahoy drush search-api:server-status -``` - -### Redis Caching Service - -```bash -# Clear all caches (includes Redis) -ahoy drush cache:rebuild - -# Check Redis connection status -ahoy drush php:script -- redis_status - -# Flush Redis cache specifically -ahoy drush eval "\\Drupal::service('cache.backend.redis')->deleteAll();" -``` - -### ClamAV Virus Scanning Service - -```bash -# Test virus scanning functionality -ahoy drush clamav:scan /path/to/test/file - -# Check ClamAV daemon status -ahoy drush clamav:status - -# Update virus definitions -ahoy drush clamav:update -``` - -## Dependency Management - -### Adding Drupal Modules - -```bash -# Add contributed modules -ahoy composer require drupal/webform - -# Enable added modules -ahoy drush pm:install webform -``` - -### Patching Contributed Modules - -When contributed modules need fixes or customizations, use the proper patching workflow with `cweagans/composer-patches`. - -Vortex uses **composer-patches v2.x** which provides git-based patching with improved reliability and reproducibility. - -#### Key Features of v2 - -- **Git-based patching**: Uses `git apply` exclusively for cross-platform consistency -- **patches.lock.json**: Automatically generated file with patch metadata and SHA-256 checksums -- **New commands**: - - `composer patches-relock` - Regenerate patches.lock.json after adding/removing patches - - `composer patches-repatch` - Remove and reinstall patched dependencies -- **Automatic failure on patch errors**: Patches must apply successfully or installation fails - -#### Understanding patches.lock.json - -This file is automatically generated and **must be committed to version control** (like composer.lock). - -**What it contains:** - -- Patch metadata (URLs, descriptions, target packages) -- SHA-256 checksums for patch verification - -**Why it matters:** - -- Ensures reproducible builds across teams and CI/CD -- Verifies patch integrity with checksums -- Prevents "works on my machine" issues -- Makes patch state explicit and trackable - -**Always commit this file** after adding or removing patches. - -#### Prerequisites for Patching - -- Project uses `cweagans/composer-patches` package (v2.x) -- Git is available for version control (required by v2) -- Access to Drupal.org git repositories - -#### Patch Storage and Configuration - -Patches are defined in `composer.json` under the `extra.patches` section: - -```json -{ - "extra": { - "patches": { - "drupal/module_name": { - "Description of fix": "patches/module-name-description.patch", - "Another fix": "https://www.drupal.org/files/issues/external-patch.patch" - } - } - } -} -``` - -- **Local patches**: Store in `patches/` directory in project root -- **External patches**: Reference URLs directly in composer.json -- **Naming convention**: Use descriptive names like `module-name-description.patch` - -#### Creating Module Patches - -Step 1: Identify Module Version - -Always work with the exact version and state used in your project: - -```bash -# Check installed version -ahoy composer show drupal/module_name - -# Verify version in composer.lock -grep -A 20 "drupal/module_name" composer.lock -``` - -Step 2: Clone Module from Git - -**CRITICAL**: Always use the official Drupal git repository, not tarball downloads: - -```bash -# Create working directory -cd /tmp && mkdir module_patch_work && cd module_patch_work - -# Clone the module -git clone https://git.drupalcode.org/project/module_name.git - -# Navigate to module directory -cd module_name - -# Checkout the exact version/tag used in project -git checkout 1.2.3 # Replace with actual version - -# Create working branch -git checkout -b fix-description -``` - -Step 3: Apply Existing Patches - -If your project already has patches for this module, apply them first to match the current state: - -```bash -# Apply each existing patch in order -curl -s https://example.com/patch1.patch | patch -p1 -curl -s https://example.com/patch2.patch | patch -p1 - -# Commit the patches to establish baseline -git add . -git commit -m "Apply existing patches to match project state" -``` - -Step 4: Make Required Changes - -Edit the necessary files to implement your fix: - -```bash -# Make your changes using your preferred editor -vim path/to/file.php - -# Or use automated tools if applicable -sed -i 's/old_code/new_code/' path/to/file.php -``` - -Step 5: Generate Clean Patch - -Create a proper git-based patch: - -```bash -# Stage your changes -git add . - -# Generate patch from staged changes -git diff --cached > /path/to/project/patches/module-name-fix-description.patch -``` - -Step 6: Test Patch Application - -**ALWAYS test that your patch applies cleanly**: - -```bash -# Reset to test patch application -git reset --hard HEAD - -# Test patch applies without conflicts -git apply /path/to/project/patches/module-name-fix-description.patch - -# Verify changes were applied correctly -git status -git diff -``` - -Step 7: Integrate into Project - -Add the patch to your project's composer configuration and apply it using v2 commands: - -```bash -# Add patch definition to composer.json extra.patches section - -# Regenerate patches.lock.json -ahoy composer patches-relock - -# Remove and reinstall patched package -ahoy composer patches-repatch - -# Update composer.lock -ahoy composer update --lock - -# Verify no patch application errors -# Check that functionality works as expected - -# Commit all changes -git add composer.json composer.lock patches.lock.json patches/ -git commit -m "Added patch for drupal/module_name." -``` - -Step 8: Clean Up - -Remove temporary working directory: - -```bash -rm -rf /tmp/module_patch_work -``` - -#### Patching Best Practices - -**โœ… Do This:** - -- Use descriptive patch names that explain what they fix -- Keep patches focused - one fix per patch when possible -- Follow Drupal coding standards in your changes -- Test locally before committing patches -- Document the issue being fixed in composer.json description -- Include issue URLs when available from drupal.org - -**โŒ Avoid These Mistakes:** - -- Working with tarball downloads instead of git repositories -- Creating patches from modified project files -- Skipping patch application testing -- Creating patches without applying existing patches first -- Assuming patches work across different module versions - -#### Troubleshooting Patch Issues - -**Patch Won't Apply:** - -1. Check module version matches between patch creation and application -2. Verify existing patches are applied in correct order -3. Check for whitespace issues in patch file -4. Ensure patch paths are correct (usually relative to module root) - -**Patch Conflicts:** - -1. Identify conflicting patches by applying them individually -2. Update patch order in composer.json if needed -3. Recreate patches against the current patched state -4. Merge patches if they modify the same areas - -**Performance Issues:** - -1. Minimize external patch URLs to reduce download time -2. Store frequently used patches locally in patches directory -3. Keep patch files small and focused -4. Remove obsolete patches when updating modules - -### Adding JavaScript/CSS Libraries - -For npm packages that need to be Drupal libraries, define them as inline -Composer packages: - -1. **Add to composer.json repositories section:** - -```json -{ - "repositories": [ - { - "type": "package", - "package": { - "name": "vendor/library-name", - "type": "drupal-library", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/vendor/library-name", - "reference": "1.0.0" - } - } - } - ] -} -``` - -1. **Install via Composer:** - -```bash -ahoy composer require vendor/library-name -``` - -### Theme Dependencies - -```bash -# Navigate to theme directory -cd web/themes/custom/[theme_name] - -# Add frontend dependencies -yarn add [package-name] - -# Example: Add Bootstrap -yarn add bootstrap - -# Install dev dependencies -yarn add --dev sass webpack -``` - -## Testing Best Practices - -### Writing Behat Tests (BDD) - -#### User Story Format (Required) - -All Behat features MUST follow this format: - -```gherkin -Feature: [Feature name] - - As a [user type] - I want to [action/goal] - So that [benefit/outcome] -``` - -#### Standard User Types - -```gherkin -As a site visitor # Anonymous users -As a site administrator # Admin users -As a content editor # Content management users -As a authenticated user # Logged-in users -``` - -#### Test Data Conventions - -- **Always prefix test content**: `[TEST] Page Title` -- **Use numbered patterns**: `[TEST] Topic 1`, `[TEST] Topic 2` -- **Avoid real names**: Don't use "Workshop" or "Training" -- **Be descriptive**: `[TEST] Event with All Fields` - -#### Example Feature File - -```gherkin -Feature: Homepage - - As a site visitor - I want to access the homepage - So that I can view the main landing page and navigate the site - - Scenario: View homepage content - Given I am on the homepage - Then I should see "[TEST] Welcome Message" - And I should see "About Us" in the "navigation" region -``` - -#### Discovering Available Step Definitions - -```bash -# Generate step definitions reference (run once) -ahoy test-bdd -- --definitions=l >.claude/artifacts/behat-steps.txt -``` - -Use the cached file for reference, don't regenerate unless asked. - -### Content Type Testing Process - -When creating comprehensive tests for content types: - -1. **Analyze Configuration First** - - - Check `config/default/field.field.node.[type].*.yml` - - Review `core.entity_view_display.node.[type].default.yml` - - Identify visible vs hidden fields - -1. **Create Supporting Entities** - -```gherkin -Background: - Given "tags" terms: - | name | - | [TEST] Topic 1 | - | [TEST] Topic 2 | - - And the following media "image" exist: - | name | - | [TEST] Featured Image 1 | -``` - -1. **Test All Visible Fields** - -```gherkin -Scenario: View complete content with all fields - Given "page" content: - | title | body | field_tags | - | [TEST] Complete Page Test | [TEST] This is the body text. | [TEST] Topic 1 | - When I visit "[TEST] Complete Page Test" - Then I should see "[TEST] Complete Page Test" - And I should see "[TEST] This is the body text." - And I should see "[TEST] Topic 1" -``` - -## Debugging & Troubleshooting - -### Development Tools - -```bash -# Enable development modules -ahoy drush pm:install devel - -# Get admin login URL -ahoy login - -# View recent log entries -ahoy drush watchdog:show - -# Clear all caches -ahoy drush cache:rebuild - -# Check system status -ahoy drush status -``` - -### Performance Optimization - -```bash -# Enable CSS/JS aggregation -ahoy drush config:set system.performance css.preprocess 1 -ahoy drush config:set system.performance js.preprocess 1 - -# Clear render cache -ahoy drush cache:rebuild-external - -# Check database updates needed -ahoy drush updatedb:status -``` - -### Container Debugging - -```bash -# Check container status -docker-compose ps - -# View container logs -docker-compose logs [service_name] -# Examples: -docker-compose logs web -docker-compose logs db - -# Access container shell -docker-compose exec web bash -docker-compose exec db mysql -u drupal -p drupal -``` - -### Common Issues & Solutions - -**Site not loading:** - -```bash -ahoy down && ahoy up -ahoy info # Verify URLs and ports -``` - -**Database connection errors:** - -```bash -docker-compose ps # Check if database container is running -ahoy reset # Nuclear option: rebuild everything -``` - -**Permission issues:** - -```bash -# Fix file permissions (Linux/Mac) -sudo chown -R $USER:$USER . -``` - -**Memory issues during composer install:** - -```bash -# Increase PHP memory temporarily -ahoy composer install --no-dev --optimize-autoloader -``` - -## CI/CD & Deployment - -### Automated Deployment - -This project includes automated deployment via: - -- **GitHub Actions** - See `.github/workflows/` - -### Hosting Platforms - -- **Lagoon** - Container-based hosting platform - -- **Container Registry** - Docker-based deployments - -### Manual Deployment Commands - -```bash -# Export configuration before deployment -ahoy drush config:export - -# Run database updates -ahoy drush updatedb - -# Import configuration -ahoy drush config:import - -# Clear caches -ahoy drush cache:rebuild - -# Full deployment sequence -ahoy drush updatedb && ahoy drush config:import && ahoy drush cache:rebuild -``` - -## Getting Help & Resources - -### Command Help - -```bash -# List all available ahoy commands -ahoy --help -``` - -### Log Files & Debugging - -```bash -# View ahoy logs -ahoy logs - -# Check container logs -docker-compose logs --tail=50 web - -# View Drupal watchdog logs -ahoy drush watchdog:show --count=20 -``` - -### Documentation Resources - -- **Vortex Documentation**: https://www.vortextemplate.com -- **Drupal Documentation**: https://www.drupal.org/docs -- **Drush Documentation**: https://www.drush.org -- **Ahoy Documentation**: https://github.com/ahoy-cli/ahoy -- **Docker Compose**: https://docs.docker.com/compose/ - -### Project-Specific Help - -- Check `/docs` directory for additional project documentation -- Review `README.md` in project root -- Check `.ahoy.yml` for custom commands -- Review `composer.json` for installed packages and scripts - ---- - - - -*This guide covers the complete development workflow for your Vortex-powered -Drupal project. Keep this guide updated as your project grows and add -project-specific conventions below.* +@AGENTS.md diff --git a/README.md b/README.md index e0cecc7b..79430446 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Drupal 11 implementation of DrevOps Website for DrevOps [//]: # (DO NOT REMOVE THE BADGE BELOW. IT IS USED BY VORTEX TO TRACK INTEGRATION) -[![Vortex](https://img.shields.io/badge/Vortex-1.35.3-65ACBC.svg)](https://github.com/drevops/vortex/tree/1.35.3) +[![Vortex](https://img.shields.io/badge/Vortex-1.36.0-65ACBC.svg)](https://github.com/drevops/vortex/tree/1.36.0) @@ -30,7 +30,7 @@ Drupal 11 implementation of DrevOps Website for DrevOps - Authenticate with Lagoon 1. Create an SSH key and add it to your account in the [Lagoon Dashboard](https://ui-lagoon-master.ch.amazee.io/). 2. Copy `.env.local.example` to `.env.local`. - 3. Update `$VORTEX_DB_DOWNLOAD_SSH_FILE` environment variable in `.env.local` file + 3. Update `$VORTEX_DOWNLOAD_DB_SSH_FILE` environment variable in `.env.local` file with the path to the SSH key. - `ahoy download-db` - `pygmy up` diff --git a/behat.yml b/behat.yml index 54dbee62..722e88fd 100644 --- a/behat.yml +++ b/behat.yml @@ -39,15 +39,10 @@ default: extra_capabilities: 'goog:chromeOptions': args: - # Options to increase stability and speed. - - '--disable-extensions' # Disables all installed Chrome extensions. Useful in testing environments to avoid interference from extensions. - - '--disable-gpu' # Disables hardware acceleration required in containers and cloud-based instances (like CI runners) where GPU is not available. - - '--disable-infobars' # Hides the infobar that Chrome displays for various notifications, like warnings when opening multiple tabs. - - '--disable-popup-blocking' # Disables the popup blocker, allowing all popups to appear. Useful in testing scenarios where popups are expected. - - '--disable-translate' # Disables the built-in translation feature, preventing Chrome from offering to translate pages. - - '--force-prefers-reduced-motion' # Forces Chrome to respect prefers-reduced-motion CSS media query, disabling animations and transitions. - - '--no-first-run' # Skips the initial setup screen that Chrome typically shows when running for the first time. - - '--test-type' # Disables certain security features and UI components that are unnecessary for automated testing, making Chrome more suitable for test environments. + - '--disable-extensions' # Prevents interference from browser extensions. + - '--disable-translate' # Prevents the translation bar from appearing on non-English pages. + - '--force-prefers-reduced-motion' # Disables CSS animations and transitions for test stability. + - '--test-type' # Suppresses error dialogs and crash recovery prompts. # Provides integration with Drupal APIs. Drupal\DrupalExtension: diff --git a/composer.json b/composer.json index b00b4d0e..833c3fdc 100644 --- a/composer.json +++ b/composer.json @@ -17,8 +17,8 @@ "drupal/coffee": "^2.0.1", "drupal/config_split": "^2.0.2", "drupal/config_update": "^2@alpha", - "drupal/core-composer-scaffold": "~11.3.2", - "drupal/core-recommended": "~11.3.2", + "drupal/core-composer-scaffold": "~11.3.3", + "drupal/core-recommended": "~11.3.3", "drupal/devel": "^5.5.0", "drupal/diff": "^1.10", "drupal/entity_clone": "^2.1@beta", @@ -62,19 +62,19 @@ "drevops/phpcs-standard": "^0.6.2", "drupal/coder": "^9@alpha", "drupal/drupal-extension": "^5.1", - "ergebnis/composer-normalize": "^2.49.0", + "ergebnis/composer-normalize": "^2.50.0", "lullabot/mink-selenium2-driver": "^1.7.4", "lullabot/php-webdriver": "^2.0.7", "mglaman/phpstan-drupal": "^2.0.10", "mikey179/vfsstream": "^1.6.12", "palantirnet/drupal-rector": "^0.21.1", "phpcompatibility/php-compatibility": "^10.0@alpha", - "phpspec/prophecy-phpunit": "^2.4", + "phpspec/prophecy-phpunit": "^2.5", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.38", - "phpunit/phpunit": "^11.5.50", + "phpstan/phpstan": "^2.1.39", + "phpunit/phpunit": "^11.5.55", "pyrech/composer-changelogs": "^2.2", - "rector/rector": "^2.3.5", + "rector/rector": "^2.3.7", "vincentlanglet/twig-cs-fixer": "^3.13" }, "conflict": { diff --git a/composer.lock b/composer.lock index 7a6d0d27..af944c99 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "568d8e68ffbeedad71d925da755e5339", + "content-hash": "474cd1297c55cbee29ed7c04a25c8450", "packages": [ { "name": "asm89/stack-cors", @@ -1589,17 +1589,17 @@ }, { "name": "drupal/ai", - "version": "1.2.8", + "version": "1.2.9", "source": { "type": "git", "url": "https://git.drupalcode.org/project/ai.git", - "reference": "1.2.8" + "reference": "1.2.9" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/ai-1.2.8.zip", - "reference": "1.2.8", - "shasum": "61edad986e872ad7255c11cd8e10e38ce1b4a492" + "url": "https://ftp.drupal.org/files/projects/ai-1.2.9.zip", + "reference": "1.2.9", + "shasum": "633b7584ab2977cd7b778d8beedeed850443793d" }, "require": { "drupal/core": "^10.4 || ^11", @@ -1626,8 +1626,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "1.2.8", - "datestamp": "1770217606", + "version": "1.2.9", + "datestamp": "1771425862", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -1659,6 +1659,10 @@ "name": "b_sharpe", "homepage": "https://www.drupal.org/user/2512258" }, + { + "name": "fago", + "homepage": "https://www.drupal.org/user/16747" + }, { "name": "gxleano", "homepage": "https://www.drupal.org/user/3591999" @@ -1675,6 +1679,10 @@ "name": "marcus_johansson", "homepage": "https://www.drupal.org/user/385947" }, + { + "name": "michaellander", + "homepage": "https://www.drupal.org/user/636494" + }, { "name": "murz", "homepage": "https://www.drupal.org/user/157092" @@ -1806,6 +1814,10 @@ "GPL-2.0-or-later" ], "authors": [ + { + "name": "a.dmitriiev", + "homepage": "https://www.drupal.org/user/3235287" + }, { "name": "b_sharpe", "homepage": "https://www.drupal.org/user/2512258" @@ -1843,17 +1855,17 @@ }, { "name": "drupal/captcha", - "version": "2.0.9", + "version": "2.0.10", "source": { "type": "git", "url": "https://git.drupalcode.org/project/captcha.git", - "reference": "2.0.9" + "reference": "2.0.10" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/captcha-2.0.9.zip", - "reference": "2.0.9", - "shasum": "15b2ba18fab75ea88bfa8f75fb1be09f7cd52cbb" + "url": "https://ftp.drupal.org/files/projects/captcha-2.0.10.zip", + "reference": "2.0.10", + "shasum": "f1052ffe99ec2f8246f7da75e467b281fc2d804a" }, "require": { "drupal/core": "^9.5 || ^10 || ^11" @@ -1861,8 +1873,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "2.0.9", - "datestamp": "1753701287", + "version": "2.0.10", + "datestamp": "1770647534", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -2409,16 +2421,16 @@ }, { "name": "drupal/core", - "version": "11.3.2", + "version": "11.3.3", "source": { "type": "git", "url": "https://github.com/drupal/core.git", - "reference": "5af5a94a47a04fdcb96608651409173b99d155a0" + "reference": "7067385162ad51020c78052fe15334671bdf3552" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core/zipball/5af5a94a47a04fdcb96608651409173b99d155a0", - "reference": "5af5a94a47a04fdcb96608651409173b99d155a0", + "url": "https://api.github.com/repos/drupal/core/zipball/7067385162ad51020c78052fe15334671bdf3552", + "reference": "7067385162ad51020c78052fe15334671bdf3552", "shasum": "" }, "require": { @@ -2464,7 +2476,7 @@ "symfony/polyfill-iconv": "^1.32", "symfony/polyfill-php84": "^1.32", "symfony/polyfill-php85": "^1.32", - "symfony/process": "^7.4", + "symfony/process": "^7.4.5", "symfony/psr-http-message-bridge": "^7.4", "symfony/routing": "^7.4", "symfony/serializer": "^7.4", @@ -2576,22 +2588,22 @@ ], "description": "Drupal is an open source content management platform powering millions of websites and applications.", "support": { - "source": "https://github.com/drupal/core/tree/11.3.2" + "source": "https://github.com/drupal/core/tree/11.3.3" }, - "time": "2026-01-08T09:19:50+00:00" + "time": "2026-02-05T08:05:30+00:00" }, { "name": "drupal/core-composer-scaffold", - "version": "11.3.2", + "version": "11.3.3", "source": { "type": "git", "url": "https://github.com/drupal/core-composer-scaffold.git", - "reference": "b8ea85a2072b52f88a1fadec28306a3946980b8e" + "reference": "445bec7d1cf903d990398b68b0a7fd98270f986f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core-composer-scaffold/zipball/b8ea85a2072b52f88a1fadec28306a3946980b8e", - "reference": "b8ea85a2072b52f88a1fadec28306a3946980b8e", + "url": "https://api.github.com/repos/drupal/core-composer-scaffold/zipball/445bec7d1cf903d990398b68b0a7fd98270f986f", + "reference": "445bec7d1cf903d990398b68b0a7fd98270f986f", "shasum": "" }, "require": { @@ -2626,29 +2638,29 @@ "drupal" ], "support": { - "source": "https://github.com/drupal/core-composer-scaffold/tree/11.3.2" + "source": "https://github.com/drupal/core-composer-scaffold/tree/11.3.3" }, - "time": "2025-10-13T16:03:31+00:00" + "time": "2026-01-26T12:09:35+00:00" }, { "name": "drupal/core-recommended", - "version": "11.3.2", + "version": "11.3.3", "source": { "type": "git", "url": "https://github.com/drupal/core-recommended.git", - "reference": "c9e8584b3754f5b0350433432f56a2cf6d4e6231" + "reference": "c5614e888df11a5e293faa97c8014a148eaefa8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core-recommended/zipball/c9e8584b3754f5b0350433432f56a2cf6d4e6231", - "reference": "c9e8584b3754f5b0350433432f56a2cf6d4e6231", + "url": "https://api.github.com/repos/drupal/core-recommended/zipball/c5614e888df11a5e293faa97c8014a148eaefa8d", + "reference": "c5614e888df11a5e293faa97c8014a148eaefa8d", "shasum": "" }, "require": { "asm89/stack-cors": "~v2.3.0", "composer/semver": "~3.4.4", "doctrine/lexer": "~3.0.1", - "drupal/core": "11.3.2", + "drupal/core": "11.3.3", "egulias/email-validator": "~4.0.4", "guzzlehttp/guzzle": "~7.10.0", "guzzlehttp/promises": "~2.3.0", @@ -2687,7 +2699,7 @@ "symfony/polyfill-mbstring": "~v1.33.0", "symfony/polyfill-php84": "~v1.33.0", "symfony/polyfill-php85": "~v1.33.0", - "symfony/process": "~v7.4.0", + "symfony/process": "~v7.4.5", "symfony/psr-http-message-bridge": "~v7.4.0", "symfony/routing": "~v7.4.0", "symfony/serializer": "~v7.4.0", @@ -2710,9 +2722,9 @@ ], "description": "Core and its dependencies with known-compatible minor versions. Require this project INSTEAD OF drupal/core.", "support": { - "source": "https://github.com/drupal/core-recommended/tree/11.3.2" + "source": "https://github.com/drupal/core-recommended/tree/11.3.3" }, - "time": "2026-01-08T09:19:50+00:00" + "time": "2026-02-05T08:05:30+00:00" }, { "name": "drupal/crop", @@ -3226,20 +3238,20 @@ }, { "name": "drupal/entity_usage", - "version": "2.0.0-beta25", + "version": "2.0.0-beta28", "source": { "type": "git", "url": "https://git.drupalcode.org/project/entity_usage.git", - "reference": "8.x-2.0-beta25" + "reference": "8.x-2.0-beta28" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/entity_usage-8.x-2.0-beta25.zip", - "reference": "8.x-2.0-beta25", - "shasum": "1579a29158648e1f65949d4aff1638ccd2c33e80" + "url": "https://ftp.drupal.org/files/projects/entity_usage-8.x-2.0-beta28.zip", + "reference": "8.x-2.0-beta28", + "shasum": "7e97367d1c583a41dfab91226e4f688309ae2ce7" }, "require": { - "drupal/core": "^10.2 || ^11" + "drupal/core": "^10.3 || ^11" }, "require-dev": { "drupal/block_field": "~1.0", @@ -3257,8 +3269,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-2.0-beta25", - "datestamp": "1762358229", + "version": "8.x-2.0-beta28", + "datestamp": "1771495229", "security-coverage": { "status": "not-covered", "message": "Beta releases are not covered by Drupal security advisories." @@ -4300,6 +4312,10 @@ "GPL-2.0-or-later" ], "authors": [ + { + "name": "acbramley", + "homepage": "https://www.drupal.org/user/1036766" + }, { "name": "berdir", "homepage": "https://www.drupal.org/user/214652" @@ -4315,6 +4331,10 @@ { "name": "greggles", "homepage": "https://www.drupal.org/user/36762" + }, + { + "name": "mably", + "homepage": "https://www.drupal.org/user/3375160" } ], "description": "Provides a mechanism for modules to automatically generate aliases for the content they manage.", @@ -6343,16 +6363,16 @@ }, { "name": "laravel/prompts", - "version": "v0.3.12", + "version": "v0.3.13", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8" + "reference": "ed8c466571b37e977532fb2fd3c272c784d7050d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/4861ded9003b7f8a158176a0b7666f74ee761be8", - "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8", + "url": "https://api.github.com/repos/laravel/prompts/zipball/ed8c466571b37e977532fb2fd3c272c784d7050d", + "reference": "ed8c466571b37e977532fb2fd3c272c784d7050d", "shasum": "" }, "require": { @@ -6396,9 +6416,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.12" + "source": "https://github.com/laravel/prompts/tree/v0.3.13" }, - "time": "2026-02-03T06:57:26+00:00" + "time": "2026-02-06T12:17:10+00:00" }, { "name": "league/container", @@ -7026,16 +7046,16 @@ }, { "name": "openai-php/client", - "version": "v0.18.0", + "version": "v0.19.0", "source": { "type": "git", "url": "https://github.com/openai-php/client.git", - "reference": "3362ab004fcfc9d77df3aff7671fbcbe70177cae" + "reference": "c52f871fba345c6cbd8a3945c4d167e2e21266fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/openai-php/client/zipball/3362ab004fcfc9d77df3aff7671fbcbe70177cae", - "reference": "3362ab004fcfc9d77df3aff7671fbcbe70177cae", + "url": "https://api.github.com/repos/openai-php/client/zipball/c52f871fba345c6cbd8a3945c4d167e2e21266fd", + "reference": "c52f871fba345c6cbd8a3945c4d167e2e21266fd", "shasum": "" }, "require": { @@ -7048,16 +7068,16 @@ "psr/http-message": "^1.1.0|^2.0.0" }, "require-dev": { - "guzzlehttp/guzzle": "^7.9.3", - "guzzlehttp/psr7": "^2.7.1", - "laravel/pint": "^1.24.0", + "guzzlehttp/guzzle": "^7.10.0", + "guzzlehttp/psr7": "^2.8.0", + "laravel/pint": "^1.25.1", "mockery/mockery": "^1.6.12", - "nunomaduro/collision": "^8.8.0", - "pestphp/pest": "^3.8.2|^4.0.0", + "nunomaduro/collision": "^8.8.3", + "pestphp/pest": "^3.8.2|^4.1.4", "pestphp/pest-plugin-arch": "^3.1.1|^4.0.0", "pestphp/pest-plugin-type-coverage": "^3.5.1|^4.0.0", - "phpstan/phpstan": "^1.12.25", - "symfony/var-dumper": "^7.2.6" + "phpstan/phpstan": "^1.12.32", + "symfony/var-dumper": "^7.3.5" }, "type": "library", "autoload": { @@ -7097,7 +7117,7 @@ ], "support": { "issues": "https://github.com/openai-php/client/issues", - "source": "https://github.com/openai-php/client/tree/v0.18.0" + "source": "https://github.com/openai-php/client/tree/v0.19.0" }, "funding": [ { @@ -7113,7 +7133,7 @@ "type": "github" } ], - "time": "2025-10-31T18:58:57+00:00" + "time": "2026-02-10T11:15:52+00:00" }, { "name": "pear/archive_tar", @@ -8072,16 +8092,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.19", + "version": "v0.12.20", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "a4f766e5c5b6773d8399711019bb7d90875a50ee" + "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/a4f766e5c5b6773d8399711019bb7d90875a50ee", - "reference": "a4f766e5c5b6773d8399711019bb7d90875a50ee", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/19678eb6b952a03b8a1d96ecee9edba518bb0373", + "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373", "shasum": "" }, "require": { @@ -8145,9 +8165,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.19" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.20" }, - "time": "2026-01-30T17:33:13+00:00" + "time": "2026-02-11T15:05:28+00:00" }, { "name": "ralouphie/getallheaders", @@ -12300,29 +12320,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.5", + "version": "1.1.6", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "conflict": { - "phpunit/phpunit": "<=7.5 || >=13" + "phpunit/phpunit": "<=7.5 || >=14" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12 || ^13", - "phpstan/phpstan": "1.4.10 || 2.1.11", + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -12342,9 +12362,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" }, - "time": "2025-04-07T20:06:18+00:00" + "time": "2026-02-07T07:09:04+00:00" }, { "name": "doctrine/instantiator", @@ -12954,16 +12974,16 @@ }, { "name": "ergebnis/composer-normalize", - "version": "2.49.0", + "version": "2.50.0", "source": { "type": "git", "url": "https://github.com/ergebnis/composer-normalize.git", - "reference": "84f3b39dd5d5847a21ec7ca83fc80af97d3ab65c" + "reference": "80971fe24ff10709789942bcbe9368b2c704097c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/composer-normalize/zipball/84f3b39dd5d5847a21ec7ca83fc80af97d3ab65c", - "reference": "84f3b39dd5d5847a21ec7ca83fc80af97d3ab65c", + "url": "https://api.github.com/repos/ergebnis/composer-normalize/zipball/80971fe24ff10709789942bcbe9368b2c704097c", + "reference": "80971fe24ff10709789942bcbe9368b2c704097c", "shasum": "" }, "require": { @@ -12979,18 +12999,18 @@ "require-dev": { "composer/composer": "^2.9.4", "ergebnis/license": "^2.7.0", - "ergebnis/php-cs-fixer-config": "^6.58.2", - "ergebnis/phpstan-rules": "^2.12.0", + "ergebnis/php-cs-fixer-config": "^6.59.0", + "ergebnis/phpstan-rules": "^2.13.1", "ergebnis/phpunit-slow-test-detector": "^2.20.0", "ergebnis/rector-rules": "^1.9.0", "fakerphp/faker": "^1.24.1", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.33", + "phpstan/phpstan": "^2.1.38", "phpstan/phpstan-deprecation-rules": "^2.0.3", "phpstan/phpstan-phpunit": "^2.0.12", - "phpstan/phpstan-strict-rules": "^2.0.7", - "phpunit/phpunit": "^9.6.20", - "rector/rector": "^2.3.4", + "phpstan/phpstan-strict-rules": "^2.0.8", + "phpunit/phpunit": "^9.6.33", + "rector/rector": "^2.3.5", "symfony/filesystem": "^5.4.41" }, "type": "composer-plugin", @@ -13034,7 +13054,7 @@ "security": "https://github.com/ergebnis/composer-normalize/blob/main/.github/SECURITY.md", "source": "https://github.com/ergebnis/composer-normalize" }, - "time": "2026-01-26T17:38:13+00:00" + "time": "2026-02-09T20:57:47+00:00" }, { "name": "ergebnis/json", @@ -13484,16 +13504,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.6.4", + "version": "v6.7.2", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7" + "reference": "6fea66c7204683af437864e7c4e7abf383d14bc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2eeb75d21cf73211335888e7f5e6fd7440723ec7", - "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/6fea66c7204683af437864e7c4e7abf383d14bc0", + "reference": "6fea66c7204683af437864e7c4e7abf383d14bc0", "shasum": "" }, "require": { @@ -13553,9 +13573,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.4" + "source": "https://github.com/jsonrainbow/json-schema/tree/v6.7.2" }, - "time": "2025-12-19T15:01:32+00:00" + "time": "2026-02-15T15:06:22+00:00" }, { "name": "localheinz/diff", @@ -14580,31 +14600,31 @@ }, { "name": "phpspec/prophecy", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "a24f1bda2d00a03877f7f99d9e6b150baf543f6d" + "reference": "7ab965042096282307992f1b9abff020095757f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/a24f1bda2d00a03877f7f99d9e6b150baf543f6d", - "reference": "a24f1bda2d00a03877f7f99d9e6b150baf543f6d", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/7ab965042096282307992f1b9abff020095757f0", + "reference": "7ab965042096282307992f1b9abff020095757f0", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2 || ^2.0", "php": "8.2.* || 8.3.* || 8.4.* || 8.5.*", "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", "symfony/deprecation-contracts": "^2.5 || ^3.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.88", + "php-cs-fixer/shim": "^3.93.1", "phpspec/phpspec": "^6.0 || ^7.0 || ^8.0", - "phpstan/phpstan": "^2.1.13", - "phpunit/phpunit": "^11.0 || ^12.0" + "phpstan/phpstan": "^2.1.13, <2.1.34 || ^2.1.39", + "phpunit/phpunit": "^11.0 || ^12.0 || ^13.0" }, "type": "library", "extra": { @@ -14645,28 +14665,28 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.24.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.25.0" }, - "time": "2025-11-21T13:10:52+00:00" + "time": "2026-02-09T11:58:00+00:00" }, { "name": "phpspec/prophecy-phpunit", - "version": "v2.4.0", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy-phpunit.git", - "reference": "d3c28041d9390c9bca325a08c5b2993ac855bded" + "reference": "89f91b01d0640b7820e427e02a007bc6489d8a26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy-phpunit/zipball/d3c28041d9390c9bca325a08c5b2993ac855bded", - "reference": "d3c28041d9390c9bca325a08c5b2993ac855bded", + "url": "https://api.github.com/repos/phpspec/prophecy-phpunit/zipball/89f91b01d0640b7820e427e02a007bc6489d8a26", + "reference": "89f91b01d0640b7820e427e02a007bc6489d8a26", "shasum": "" }, "require": { "php": "^7.3 || ^8", "phpspec/prophecy": "^1.18", - "phpunit/phpunit": "^9.1 || ^10.1 || ^11.0 || ^12.0" + "phpunit/phpunit": "^9.1 || ^10.1 || ^11.0 || ^12.0 || ^13.0" }, "require-dev": { "phpstan/phpstan": "^1.10" @@ -14700,9 +14720,9 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy-phpunit/issues", - "source": "https://github.com/phpspec/prophecy-phpunit/tree/v2.4.0" + "source": "https://github.com/phpspec/prophecy-phpunit/tree/v2.5.0" }, - "time": "2025-05-13T13:52:32+00:00" + "time": "2026-02-09T15:40:55+00:00" }, { "name": "phpstan/extension-installer", @@ -14801,11 +14821,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.38", + "version": "2.1.39", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629", - "reference": "dfaf1f530e1663aa167bc3e52197adb221582629", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", + "reference": "c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", "shasum": "" }, "require": { @@ -14850,25 +14870,25 @@ "type": "github" } ], - "time": "2026-01-30T17:12:46+00:00" + "time": "2026-02-11T14:48:56+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "468e02c9176891cc901143da118f09dc9505fc2f" + "reference": "6b5571001a7f04fa0422254c30a0017ec2f2cacc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/468e02c9176891cc901143da118f09dc9505fc2f", - "reference": "468e02c9176891cc901143da118f09dc9505fc2f", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/6b5571001a7f04fa0422254c30a0017ec2f2cacc", + "reference": "6b5571001a7f04fa0422254c30a0017ec2f2cacc", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.15" + "phpstan/phpstan": "^2.1.39" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -14893,11 +14913,14 @@ "MIT" ], "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "keywords": [ + "static analysis" + ], "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.3" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.4" }, - "time": "2025-05-14T10:56:57+00:00" + "time": "2026-02-09T13:21:14+00:00" }, { "name": "phpunit/php-code-coverage", @@ -15248,16 +15271,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.50", + "version": "11.5.55", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", "shasum": "" }, "require": { @@ -15272,7 +15295,7 @@ "phar-io/version": "^3.2.1", "php": ">=8.2", "phpunit/php-code-coverage": "^11.0.12", - "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-file-iterator": "^5.1.1", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", @@ -15284,6 +15307,7 @@ "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" @@ -15329,7 +15353,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" }, "funding": [ { @@ -15353,7 +15377,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T05:59:18+00:00" + "time": "2026-02-18T12:37:06+00:00" }, { "name": "pyrech/composer-changelogs", @@ -15415,21 +15439,21 @@ }, { "name": "rector/rector", - "version": "2.3.5", + "version": "2.3.7", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "9442f4037de6a5347ae157fe8e6c7cda9d909070" + "reference": "9c46ad17f57963932c9788fd1b0f1d07ff450370" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/9442f4037de6a5347ae157fe8e6c7cda9d909070", - "reference": "9442f4037de6a5347ae157fe8e6c7cda9d909070", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/9c46ad17f57963932c9788fd1b0f1d07ff450370", + "reference": "9c46ad17f57963932c9788fd1b0f1d07ff450370", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.36" + "phpstan/phpstan": "^2.1.38" }, "conflict": { "rector/rector-doctrine": "*", @@ -15463,7 +15487,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.3.5" + "source": "https://github.com/rectorphp/rector/tree/2.3.7" }, "funding": [ { @@ -15471,7 +15495,7 @@ "type": "github" } ], - "time": "2026-01-28T15:22:48+00:00" + "time": "2026-02-19T14:44:16+00:00" }, { "name": "sebastian/cli-parser", @@ -17165,16 +17189,16 @@ }, { "name": "webmozart/assert", - "version": "2.1.2", + "version": "2.1.5", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649" + "reference": "79155f94852fa27e2f73b459f6503f5e87e2c188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", - "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/79155f94852fa27e2f73b459f6503f5e87e2c188", + "reference": "79155f94852fa27e2f73b459f6503f5e87e2c188", "shasum": "" }, "require": { @@ -17221,9 +17245,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.1.2" + "source": "https://github.com/webmozarts/assert/tree/2.1.5" }, - "time": "2026-01-13T14:02:24+00:00" + "time": "2026-02-18T14:09:36+00:00" } ], "aliases": [], diff --git a/docs/README.md b/docs/README.md index 44c9ca7e..fdb1862c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,11 @@ -# Project-specific documentation +# Project documentation -- [Testing](testing.md) -- [CI](ci.md) -- [Deployment](deployment.md) -- [Releasing](releasing.md) -- [FAQs](faqs.md) +This directory contains project-specific documentation that describes **what** +applies to this project. For **how** to perform operations, see +[Vortex Documentation](https://www.vortextemplate.com/docs/). + +- [Testing](testing.md) - Testing conventions and agreements +- [CI](ci.md) - Continuous integration configuration +- [Deployment](deployment.md) - Deployment configuration +- [Releasing](releasing.md) - Release process and versioning +- [FAQs](faqs.md) - Project-specific FAQs diff --git a/docs/ci.md b/docs/ci.md index cbbca01c..265078de 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -1,30 +1,15 @@ # Continuous Integration -In software engineering, continuous integration (CI) is the practice of merging -all developer working copies to a shared mainline several times a day. -Before feature changes can be merged into a shared mainline, a complete build -must run and pass all tests on CI server. +For information on how CI works, see +[Vortex CI Documentation](https://www.vortextemplate.com/docs/continuous-integration). -## GitHub Actions +## CI provider -This project uses [GitHub Actions](https://github.com/features/actions) as a -CI server: it imports production backups into fully built codebase and runs -code linting and tests. When tests pass, a deployment process is triggered for -nominated branches (usually, `main` and `develop`). +This project uses [GitHub Actions](https://github.com/features/actions). -Refer to https://www.vortextemplate.com/docs/continuous-integration for more -information. +See [GitHub Actions documentation](https://www.vortextemplate.com/docs/continuous-integration/github-actions) +for setup and configuration details. -### Skipping CI build +## Project-specific configuration -Add `[skip ci]` to the commit subject to skip CI build. Useful for documentation -changes. - -### SSH - -GitHub Actions does not supports shell access to the build, but there is an -action provided withing the `build` job that allows you to run a build with SSH -support. - -Use "Run workflow" button in GitHub Actions UI to start build with SSH support -that will be available for 120 minutes after the build is finished. + diff --git a/docs/deployment.md b/docs/deployment.md index 07719dd6..58f62cd6 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,27 +1,23 @@ -# Deployment process +# Deployment -Refer to https://www.vortextemplate.com/docs/workflows/deployment for more information. +For information on how deployment works, see +[Vortex Deployment Documentation](https://www.vortextemplate.com/docs/deployment). -## Workflow +## Hosting provider -1. Code is authored on a local machine by a developer. -2. Once the code is ready, it is pushed to GitHub. A pull request needs to be - opened for this code change. -3. The CI "listens" for code changes and will start an automated build. -4. At the end of the build, when all tests have passed, the CI will trigger a - deployment to Lagoon. -5. Once deployed, a PR environment will appear with a PR name. The database will - be taken from production environment. - All pending update hooks and other deployment operations will run during - deployment. +This project is hosted on [Lagoon](https://www.amazee.io/lagoon). -Once PR is closed, the environment will be automatically removed. +See [Lagoon hosting documentation](https://www.vortextemplate.com/docs/hosting/lagoon) +for setup and configuration details. -## Database refresh in Lagoon environments +### Database refresh -To refresh the database in the existing Lagoon environment with the database from -production environment, run: +To refresh the database in an existing Lagoon environment with production data: ```bash VORTEX_DEPLOY_BRANCH= VORTEX_DEPLOY_ACTION=deploy_override_db ahoy deploy ``` + +## Project-specific configuration + + diff --git a/docs/faqs.md b/docs/faqs.md index daee559d..03f0de6c 100644 --- a/docs/faqs.md +++ b/docs/faqs.md @@ -1,168 +1,8 @@ # FAQs -## How to know which commands are available? +For common questions and answers, see +[Vortex FAQs](https://www.vortextemplate.com/docs/development/faqs). -```bash -ahoy help -``` +## Project-specific FAQs -## How to pass CLI arguments to commands? - -```bash -ahoy mycommand -- myarg1 myarg2 --myoption1 --myoption2=myvalue -``` - -## How to clear Drupal cache? - -```bash -ahoy drush cr -``` - -## How to login to a Drupal site? - -```bash -ahoy login -``` - -## How to connect to the database? - -If you have [Sequel Ace](https://sequel-ace.com/): - -```bash -ahoy db -``` - -Otherwise: - -1. Run `ahoy info` and grab the DB host port number. -2. Use these connection details: - -- Host: `127.0.0.1` -- Username: `drupal` -- Password: `drupal` -- Database: `drupal` -- Port: the port from step 1 - -## How to use Xdebug? - -1. Run `ahoy debug`. -2. Enable listening for incoming debug connections in your IDE. -3. If required, provide server URL to your IDE as it appears in the browser. -4. Enable Xdebug flag in the request coming from your web browser (use one of - the extensions or add `?XDEBUG_SESSION_START=1` to your URL). -5. Set a breakpoint in your IDE and perform a request in the web browser. - -Use the same commands to debug CLI scripts. - -To disable, run `ahoy up`. - -See https://www.vortextemplate.com/docs/tools/xdebug for more details. - -## How to use Xdebug on Behat scripts? - -1. Enable debugging: `ahoy debug` -2. Enter CLI container: `ahoy cli` -3. Run Behat tests: - -```bash -vendor/bin/behat --xdebug path/to/test.feature -``` - -## What should I do to switch to a "clean" branch environment? - -Provided that your stack is already running: - -1. Switch to your branch -2. `composer install` -3. `ahoy provision` - -You do not need to rebuild the full stack using `ahoy build` every -time you switch branches. `ahoy provision` will update the environment -to match the current branch. - -However, sometimes you would want to have an absolutely clean environment - in -that case, use `ahoy build`. - -## How to just import a database? - -Provided that your stack is already running: - -```bash -ahoy import-db - -ahoy import-db .data/db_custom.sql -``` - -## How to add Drupal modules - -```bash -composer require drupal/module_name -``` - -## How to add patches for Drupal modules - -1. Add `title` to patch on https://drupal.org to the `patches` array in `extra` - section in `composer.json`. - - ```json - "extra": { - "patches": { - "drupal/somepackage": { - "Remote patch description": "https://www.drupal.org/files/issues/issue.patch" - "Local patch description": "patches/package-description.patch" - } - } - } - ``` - -2. Run - - ```bash - composer require drupal/somepackage - ``` - -See https://www.vortextemplate.com/docs/workflows/development#working-with-composer-packages - -## What should I do when Composer blocks package installation due to security vulnerabilities? - -Starting with Composer 2.9.0, Composer can automatically block the installation -or update of packages with known security vulnerabilities. If you encounter this -issue, you have several options: - -1. **Update the affected packages**: Try updating to newer versions that don't - have known vulnerabilities: `composer update vendor/package-name` -2. **Review the vulnerability**: Run `composer audit` to see details about the - security issues and determine if they affect your project. -3. **Adjust security settings**: If you need to proceed with installation despite - warnings (for example, if no secure version is available or the vulnerability - doesn't affect your use case), you can configure the `audit.block-insecure` - setting in your `composer.json`. - -See the [Composer configuration documentation](https://www.vortextemplate.com/docs/drupal/composer#config) -for detailed information about the `audit` configuration option and guidance on -choosing the right security settings for your project. - -## How to set a custom maintenance theme? - -To set a custom theme for Drupal's maintenance mode (when the site is offline -for updates), set the `DRUPAL_MAINTENANCE_THEME` environment variable: - -```bash -# In .env file -DRUPAL_MAINTENANCE_THEME=my_custom_theme -``` - -This theme will be used when Drupal is in maintenance mode. If -`DRUPAL_MAINTENANCE_THEME` is not set, the system will fall back to using the -value of `DRUPAL_THEME`. - -The maintenance theme should be a valid Drupal theme that is already installed -and enabled on your site. - -## Behat tests with `@javascript` tag sometimes get stuck - -Behat tests with `@javascript` tag sometimes get stuck for about 10min then -fail. -The Chrome container randomly get stuck for an unknown reason. - -Restart the Chrome container: `docker compose restart chrome` + diff --git a/docs/releasing.md b/docs/releasing.md index 84fc9caf..bb6c00eb 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -1,37 +1,30 @@ # Releasing -[git-flow](https://danielkummer.github.io/git-flow-cheatsheet/) is used to -manage releases. - -Note: after completing the release, commits must be manually pushed -from `main` to `production` branch. - -Refer to https://www.vortextemplate.com/docs/workflows/releasing for more information. +For information on how releasing works, see +[Vortex Releasing Documentation](https://www.vortextemplate.com/docs/releasing). ## Release outcome +After a successful release: + 1. Release branch exists as `release/X.Y.Z` in GitHub repository. 2. Release tag exists as `X.Y.Z` in GitHub repository. 3. The `HEAD` of the `main` branch has `X.Y.Z` tag. 4. The hash of the `HEAD` of the `main` branch exists in the `develop` branch. - This is to ensure that everything pushed to `main` exists in `develop` (in - case if `main` had any hot-fixes that not yet have been merged - to `develop`). 5. There are no PRs in GitHub related to the release. 6. The hash of the `HEAD` of the `production` branch matches the hash of the `HEAD` of `main` branch. -## Version Number - Calendar Versioning (CalVer) +## Version scheme -Release versions are numbered according to [CalVer Versioning](https://calver.org/). +This project uses [Calendar Versioning](https://calver.org/) (`YY.M.Z`): -Given a version number `YY.M.Z`: +- `YY` = Short year +- `M` = Short month +- `Z` = Hotfix/patch version -- `YY` = Short year. No leading zeroes. -- `M` = Short month. No leading zeroes. -- `Z` = Hotfix/patch version. No leading zeroes. +Examples: `25.1.0`, `25.11.1`, `25.1.10`, `25.10.1` -Examples: +## Project-specific configuration -- Correct: `25.1.0`, `25.11.1` , `25.1.10`, `25.10.1`, `9.12.0` -- Incorrect: `25.0.0`, `2025.1.1` , `25` , `25.1.00` , `25.01.0`, `25.0.0`, `01.1.0` + diff --git a/docs/testing.md b/docs/testing.md index 0ffbd16a..4e02811d 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -1,3 +1,228 @@ # Testing -Refer to https://www.vortextemplate.com/docs/workflows/testing for more information. +This document describes **what** testing conventions and agreements apply to +this project. + +## PHPUnit conventions + +Unit, Kernel, Functional tests. + +See [documentation](https://www.vortextemplate.com/docs/development/phpunit) +on how to run tests, configure environment variables and code coverage, and use +test reports in continuous integration pipeline. + +### Test class structure + +All PHPUnit tests must follow this structure: + +```php +assertEquals('expected', $result); + } + +} +``` + +### Base test classes + +- **Unit** - `Drupal\Tests\UnitTestCase` - Testing isolated PHP classes +- **Kernel** - `Drupal\KernelTests\KernelTestBase` - Testing with database/services +- **Functional** - `Drupal\Tests\BrowserTestBase` - Testing with full browser +- **FunctionalJavascript** - `Drupal\FunctionalJavascriptTests\WebDriverTestBase` - Testing browser interaction with JavaScript + +### Test data conventions + +- **Always prefix test content**: `[TEST] Node Title` +- **Use data providers**: For testing multiple input/output combinations + - Must be a public static method + - Must follow naming convention `dataProvider` + - Must be placed after the test method it provides data for. +- **Use unique identifiers**: Include test class or method name in test data + +### Data providers + +Always use data providers for testing multiple input/output combinations: + +```php +/** + * Test my function with various inputs. + * + * @dataProvider dataProviderMyFunction + */ +public function testMyFunction(string $input, string $expected): void { + $this->assertEquals($expected, my_function($input)); +} + +/** + * Data provider for testMyFunction. + */ +public static function dataProviderMyFunction(): array { + return [ + 'empty string' => ['', ''], + 'simple string' => ['hello', 'HELLO'], + 'with numbers' => ['test123', 'TEST123'], + ]; +} +``` + +### Testing services (Kernel tests) + +For Kernel tests that need Drupal services: + +```php +installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->myService = $this->container->get('my_module.my_service'); + } + + /** + * Test service method. + */ + public function testServiceMethod(): void { + $result = $this->myService->doSomething(); + $this->assertNotNull($result); + } + +} +``` + +## Behat conventions + +BDD end-to-end tests. + +See [documentation](https://www.vortextemplate.com/docs/development/behat) +on how to run Behat tests, configure environment variables, and use test reports +in continuous integration pipeline. + +### User story format + +All Behat features must follow this format: + +```gherkin +Feature: [Feature name] + + As a [user type] + I want to [action/goal] + So that [benefit/outcome] +``` + +### Standard user types + +Use these consistent user type descriptions: + +```gherkin +As a site visitor # Anonymous users +As a site administrator # Admin users +As a content editor # Content management users +As a authenticated user # Logged-in users +``` + +### Test data conventions + +- **Always prefix test content**: `[TEST] Page Title` +- **Use numbered patterns**: `[TEST] Topic 1`, `[TEST] Topic 2` +- **Avoid real names**: Don't use "Workshop" or "Training" +- **Be descriptive**: `[TEST] Event with All Fields` + +### Example feature file + +```gherkin +Feature: Homepage + + As a site visitor + I want to access the homepage + So that I can view the main landing page and navigate the site + + Scenario: View homepage content + Given I am on the homepage + Then I should see "[TEST] Welcome Message" + And I should see "About Us" in the "navigation" region +``` + +### Content type testing process + +When creating comprehensive tests for content types: + +1. Analyze configuration first + + - Check `config/default/field.field.node.[type].*.yml` + - Review `core.entity_view_display.node.[type].default.yml` + - Identify visible vs hidden fields + +2. Create supporting entities + + ```gherkin + Background: + Given "tags" terms: + | name | + | [TEST] Topic 1 | + | [TEST] Topic 2 | + + And the following media "image" exist: + | name | + | [TEST] Featured Image 1 | + ``` + +3. Test all visible fields + + ```gherkin + Scenario: View complete content with all fields + Given "page" content: + | title | body | field_tags | + | [TEST] Complete Page Test | [TEST] This is the body text. | [TEST] Topic 1 | + When I visit "[TEST] Complete Page Test" + Then I should see "[TEST] Complete Page Test" + And I should see "[TEST] This is the body text." + And I should see "[TEST] Topic 1" + ``` diff --git a/drush/php-ini/drush.ini b/drush/php-ini/drush.ini new file mode 100644 index 00000000..97b9aa9f --- /dev/null +++ b/drush/php-ini/drush.ini @@ -0,0 +1,9 @@ +; Custom PHP runtime configuration for Drush CLI commands. +; +; This file is auto-discovered via the PHP_INI_SCAN_DIR environment variable. +; Modify the values below to adjust PHP runtime settings for Drush commands. +; +; @see https://github.com/drevops/vortex/issues/1913 + +; Increase memory limit for Drush operations (database imports, config sync). +memory_limit = 512M diff --git a/package.json b/package.json index bdd8dc9a..ec0431ad 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "private": true, "engines": { "yarn": ">= 1.22.22", - "node": ">= 20.0" + "node": ">= 20.20.0" }, "scripts": { "lint-js": "eslint web/modules/custom --ext .js --max-warnings=0 --no-error-on-unmatched-pattern", diff --git a/phpunit.xml b/phpunit.xml index 3182b008..9efac315 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,7 +9,7 @@ beStrictAboutChangesToGlobalState="true" beStrictAboutOutputDuringTests="true" beStrictAboutTestsThatDoNotTestAnything="true" - bootstrap="web/core/tests/bootstrap.php" + bootstrap="tests/phpunit/bootstrap.php" cacheResult="false" colors="true" displayDetailsOnPhpunitDeprecations="true" @@ -31,38 +31,37 @@ - - - - + + - + + - - + + @@ -79,13 +78,10 @@ web/themes/custom/**/tests/src/Functional - - - @@ -123,4 +119,14 @@ skipped + + + + + + + diff --git a/renovate.json b/renovate.json index 0380c85e..7fef9a4b 100644 --- a/renovate.json +++ b/renovate.json @@ -10,11 +10,12 @@ "ignorePresets": [ ":prHourlyLimit2" ], - "rangeStrategy": "update-lockfile", + "rangeStrategy": "bump", "timezone": "Australia/Melbourne", "configMigration": true, "enabledManagers": [ "composer", + "npm", "dockerfile", "docker-compose", "github-actions", @@ -25,6 +26,9 @@ "matchDepNames": [ "php" ], + "matchFileNames": [ + "composer.json" + ], "enabled": false }, { @@ -32,6 +36,9 @@ "matchDatasources": [ "packagist" ], + "matchFileNames": [ + "composer.json" + ], "matchUpdateTypes": [ "major" ], @@ -46,6 +53,9 @@ "matchDatasources": [ "packagist" ], + "matchFileNames": [ + "composer.json" + ], "separateMinorPatch": false, "schedule": [ "before 2am on Sunday" @@ -61,6 +71,9 @@ { "groupName": "Minor and Patch Core", "groupSlug": "drupal-minor-patch-core", + "matchFileNames": [ + "composer.json" + ], "schedule": [ "before 2am" ], @@ -78,9 +91,58 @@ "drupal/core-dev" ] }, + { + "groupName": "Non-root npm - skipped", + "matchDatasources": [ + "npm" + ], + "matchFileNames": [ + "!package.json" + ], + "enabled": false, + "matchPackageNames": [ + "/.*/" + ] + }, + { + "groupName": "Major npm - skipped to update manually", + "matchDatasources": [ + "npm" + ], + "matchFileNames": [ + "package.json" + ], + "matchUpdateTypes": [ + "major" + ], + "enabled": false, + "matchPackageNames": [ + "/.*/" + ] + }, + { + "groupName": "Minor and Patch npm", + "groupSlug": "npm-minor-patch", + "matchDatasources": [ + "npm" + ], + "matchFileNames": [ + "package.json" + ], + "separateMinorPatch": false, + "schedule": [ + "before 2am on Sunday" + ], + "matchPackageNames": [ + "/.*/" + ] + }, { "groupName": "Container images", "groupSlug": "docker", + "matchFileNames": [ + ".docker/**" + ], "schedule": [ "before 3am" ], diff --git a/scripts/vortex/deploy-artifact.sh b/scripts/vortex/deploy-artifact.sh index 8a02cb67..82014c1a 100755 --- a/scripts/vortex/deploy-artifact.sh +++ b/scripts/vortex/deploy-artifact.sh @@ -46,10 +46,10 @@ VORTEX_DEPLOY_ARTIFACT_DST_BRANCH="${VORTEX_DEPLOY_ARTIFACT_DST_BRANCH:-[branch] VORTEX_DEPLOY_ARTIFACT_LOG="${VORTEX_DEPLOY_ARTIFACT_LOG:-${VORTEX_DEPLOY_ARTIFACT_ROOT}/deployment_log.txt}" # SSH key fingerprint used to connect to remote. -VORTEX_DEPLOY_SSH_FINGERPRINT="${VORTEX_DEPLOY_SSH_FINGERPRINT:-}" +VORTEX_DEPLOY_ARTIFACT_SSH_FINGERPRINT="${VORTEX_DEPLOY_ARTIFACT_SSH_FINGERPRINT:-${VORTEX_DEPLOY_SSH_FINGERPRINT:-}}" # Default SSH file used if custom fingerprint is not provided. -VORTEX_DEPLOY_SSH_FILE="${VORTEX_DEPLOY_SSH_FILE:-${HOME}/.ssh/id_rsa}" +VORTEX_DEPLOY_ARTIFACT_SSH_FILE="${VORTEX_DEPLOY_ARTIFACT_SSH_FILE:-${VORTEX_DEPLOY_SSH_FILE:-${HOME}/.ssh/id_rsa}}" # ------------------------------------------------------------------------------ @@ -76,7 +76,7 @@ info "Started ARTIFACT deployment." [ "$(git config --global user.name)" = "" ] && task "Configuring global git user name." && git config --global user.name "${VORTEX_DEPLOY_ARTIFACT_GIT_USER_NAME}" [ "$(git config --global user.email)" = "" ] && task "Configuring global git user email." && git config --global user.email "${VORTEX_DEPLOY_ARTIFACT_GIT_USER_EMAIL}" -export VORTEX_SSH_PREFIX="DEPLOY" && . ./scripts/vortex/setup-ssh.sh +export VORTEX_SSH_PREFIX="DEPLOY_ARTIFACT" && . ./scripts/vortex/setup-ssh.sh task "Installing artifact builder." composer global require --dev -n --ansi --prefer-source --ignore-platform-reqs drevops/git-artifact:~1.2 diff --git a/scripts/vortex/deploy-lagoon.sh b/scripts/vortex/deploy-lagoon.sh index 3a0a4b0e..5dd883a0 100755 --- a/scripts/vortex/deploy-lagoon.sh +++ b/scripts/vortex/deploy-lagoon.sh @@ -13,28 +13,33 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ] set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x +# Deployment mode. +# +# Values can be one of: branch, tag. +VORTEX_DEPLOY_MODE="${VORTEX_DEPLOY_MODE:-branch}" + # Deployment action. # # Values can be one of: deploy, deploy_override_db, destroy. # - deploy: Deploy code and preserve database in the environment. # - deploy_override_db: Deploy code and override database in the environment. # - destroy: Destroy the environment (if the provider supports it). -VORTEX_DEPLOY_ACTION="${VORTEX_DEPLOY_ACTION:-create}" +VORTEX_DEPLOY_LAGOON_ACTION="${VORTEX_DEPLOY_LAGOON_ACTION:-${VORTEX_DEPLOY_ACTION:-create}}" # The Lagoon project to perform deployment for. -LAGOON_PROJECT="${LAGOON_PROJECT:-}" +VORTEX_DEPLOY_LAGOON_PROJECT="${VORTEX_DEPLOY_LAGOON_PROJECT:-${LAGOON_PROJECT:-}}" # The Lagoon branch to deploy. -VORTEX_DEPLOY_BRANCH="${VORTEX_DEPLOY_BRANCH:-}" +VORTEX_DEPLOY_LAGOON_BRANCH="${VORTEX_DEPLOY_LAGOON_BRANCH:-${VORTEX_DEPLOY_BRANCH:-}}" # The PR number to deploy. -VORTEX_DEPLOY_PR="${VORTEX_DEPLOY_PR:-}" +VORTEX_DEPLOY_LAGOON_PR="${VORTEX_DEPLOY_LAGOON_PR:-${VORTEX_DEPLOY_PR:-}}" # The PR head branch to deploy. -VORTEX_DEPLOY_PR_HEAD="${VORTEX_DEPLOY_PR_HEAD:-}" +VORTEX_DEPLOY_LAGOON_PR_HEAD="${VORTEX_DEPLOY_LAGOON_PR_HEAD:-${VORTEX_DEPLOY_PR_HEAD:-}}" # The PR base branch (the branch the PR is raised against). Defaults to 'develop'. -VORTEX_DEPLOY_PR_BASE_BRANCH="${VORTEX_DEPLOY_PR_BASE_BRANCH:-develop}" +VORTEX_DEPLOY_LAGOON_PR_BASE_BRANCH="${VORTEX_DEPLOY_LAGOON_PR_BASE_BRANCH:-${VORTEX_DEPLOY_PR_BASE_BRANCH:-develop}}" # The Lagoon instance name to interact with. VORTEX_DEPLOY_LAGOON_INSTANCE="${VORTEX_DEPLOY_LAGOON_INSTANCE:-amazeeio}" @@ -54,19 +59,19 @@ VORTEX_DEPLOY_LAGOON_INSTANCE_PORT="${VORTEX_DEPLOY_LAGOON_INSTANCE_PORT:-32222} # In most cases, the default SSH key does not work (because it is a read-only # key used by CircleCI to checkout code from git), so you should add another # deployment key. -VORTEX_DEPLOY_SSH_FINGERPRINT="${VORTEX_DEPLOY_SSH_FINGERPRINT:-}" +VORTEX_DEPLOY_LAGOON_SSH_FINGERPRINT="${VORTEX_DEPLOY_LAGOON_SSH_FINGERPRINT:-${VORTEX_DEPLOY_SSH_FINGERPRINT:-}}" # Default SSH file used if custom fingerprint is not provided. -VORTEX_DEPLOY_SSH_FILE="${VORTEX_DEPLOY_SSH_FILE:-${HOME}/.ssh/id_rsa}" +VORTEX_DEPLOY_LAGOON_SSH_FILE="${VORTEX_DEPLOY_LAGOON_SSH_FILE:-${VORTEX_DEPLOY_SSH_FILE:-${HOME}/.ssh/id_rsa}}" # Location of the Lagoon CLI binary. -VORTEX_LAGOONCLI_PATH="${VORTEX_LAGOONCLI_PATH:-/tmp}" +VORTEX_DEPLOY_LAGOON_LAGOONCLI_PATH="${VORTEX_DEPLOY_LAGOON_LAGOONCLI_PATH:-${VORTEX_LAGOONCLI_PATH:-/tmp}}" # Flag to force the installation of Lagoon CLI. -VORTEX_LAGOONCLI_FORCE_INSTALL="${VORTEX_LAGOONCLI_FORCE_INSTALL:-}" +VORTEX_DEPLOY_LAGOON_LAGOONCLI_FORCE_INSTALL="${VORTEX_DEPLOY_LAGOON_LAGOONCLI_FORCE_INSTALL:-${VORTEX_LAGOONCLI_FORCE_INSTALL:-}}" # Lagoon CLI version to use. -VORTEX_LAGOONCLI_VERSION="${VORTEX_LAGOONCLI_VERSION:-v0.32.0}" +VORTEX_DEPLOY_LAGOON_LAGOONCLI_VERSION="${VORTEX_DEPLOY_LAGOON_LAGOONCLI_VERSION:-${VORTEX_LAGOONCLI_VERSION:-v0.32.0}}" # Flag to control failure behavior when Lagoon environment limits are exceeded. # When set to 0, the deployment will exit with success instead of failure. @@ -95,25 +100,32 @@ exit_code=0 info "Started LAGOON deployment." +# Lagoon does not support tag deployments. Exit successfully with a message. +if [ "${VORTEX_DEPLOY_MODE}" = "tag" ]; then + note "Lagoon does not support tag deployments. Skipping." + pass "Finished LAGOON deployment." + exit 0 +fi + ## Check all required values. -[ -z "${LAGOON_PROJECT}" ] && fail "Missing required value for LAGOON_PROJECT." && exit 1 -{ [ -z "${VORTEX_DEPLOY_BRANCH}" ] && [ -z "${VORTEX_DEPLOY_PR}" ]; } && fail "Missing required value for VORTEX_DEPLOY_BRANCH or VORTEX_DEPLOY_PR." && exit 1 +[ -z "${VORTEX_DEPLOY_LAGOON_PROJECT}" ] && fail "Missing required value for VORTEX_DEPLOY_LAGOON_PROJECT or LAGOON_PROJECT." && exit 1 +{ [ -z "${VORTEX_DEPLOY_LAGOON_BRANCH}" ] && [ -z "${VORTEX_DEPLOY_LAGOON_PR}" ]; } && fail "Missing required value for VORTEX_DEPLOY_LAGOON_BRANCH or VORTEX_DEPLOY_BRANCH or VORTEX_DEPLOY_LAGOON_PR or VORTEX_DEPLOY_PR." && exit 1 -export VORTEX_SSH_PREFIX="DEPLOY" && . ./scripts/vortex/setup-ssh.sh +export VORTEX_SSH_PREFIX="DEPLOY_LAGOON" && . ./scripts/vortex/setup-ssh.sh -if ! command -v lagoon >/dev/null || [ -n "${VORTEX_LAGOONCLI_FORCE_INSTALL}" ]; then +if ! command -v lagoon >/dev/null || [ -n "${VORTEX_DEPLOY_LAGOON_LAGOONCLI_FORCE_INSTALL}" ]; then task "Installing Lagoon CLI." platform=$(uname -s | tr '[:upper:]' '[:lower:]') arch_suffix=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') - download_url="https://github.com/uselagoon/lagoon-cli/releases/download/${VORTEX_LAGOONCLI_VERSION}/lagoon-cli-${VORTEX_LAGOONCLI_VERSION}-${platform}-${arch_suffix}" + download_url="https://github.com/uselagoon/lagoon-cli/releases/download/${VORTEX_DEPLOY_LAGOON_LAGOONCLI_VERSION}/lagoon-cli-${VORTEX_DEPLOY_LAGOON_LAGOONCLI_VERSION}-${platform}-${arch_suffix}" note "Downloading Lagoon CLI from ${download_url}." - curl -fSLs -o "${VORTEX_LAGOONCLI_PATH}/lagoon" "${download_url}" + curl -fSLs -o "${VORTEX_DEPLOY_LAGOON_LAGOONCLI_PATH}/lagoon" "${download_url}" - note "Installing Lagoon CLI to ${VORTEX_LAGOONCLI_PATH}/lagoon." - chmod +x "${VORTEX_LAGOONCLI_PATH}/lagoon" - export PATH="${PATH}:${VORTEX_LAGOONCLI_PATH}" + note "Installing Lagoon CLI to ${VORTEX_DEPLOY_LAGOON_LAGOONCLI_PATH}/lagoon." + chmod +x "${VORTEX_DEPLOY_LAGOON_LAGOONCLI_PATH}/lagoon" + export PATH="${PATH}:${VORTEX_DEPLOY_LAGOON_LAGOONCLI_PATH}" fi for cmd in lagoon curl; do command -v "${cmd}" >/dev/null || { @@ -125,19 +137,19 @@ task "Configuring Lagoon instance." #shellcheck disable=SC2218 lagoon config add --force --lagoon "${VORTEX_DEPLOY_LAGOON_INSTANCE}" --graphql "${VORTEX_DEPLOY_LAGOON_INSTANCE_GRAPHQL}" --hostname "${VORTEX_DEPLOY_LAGOON_INSTANCE_HOSTNAME}" --port "${VORTEX_DEPLOY_LAGOON_INSTANCE_PORT}" -lagoon() { command lagoon --force --skip-update-check --ssh-key "${VORTEX_DEPLOY_SSH_FILE}" --lagoon "${VORTEX_DEPLOY_LAGOON_INSTANCE}" --project "${LAGOON_PROJECT}" "$@"; } +lagoon() { command lagoon --force --skip-update-check --ssh-key "${VORTEX_DEPLOY_LAGOON_SSH_FILE}" --lagoon "${VORTEX_DEPLOY_LAGOON_INSTANCE}" --project "${VORTEX_DEPLOY_LAGOON_PROJECT}" "$@"; } # ACTION: 'destroy' # Explicitly specifying "destroy" action as a failsafe. -if [ "${VORTEX_DEPLOY_ACTION}" = "destroy" ]; then - task "Destroying environment: project ${LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_BRANCH}." - lagoon delete environment --environment "${VORTEX_DEPLOY_BRANCH}" || true +if [ "${VORTEX_DEPLOY_LAGOON_ACTION}" = "destroy" ]; then + task "Destroying environment: project ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}." + lagoon delete environment --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" || true # ACTION: 'deploy' OR 'deploy_override_db' else # Deploy PR. - if [ -n "${VORTEX_DEPLOY_PR:-}" ]; then - deploy_pr_full="pr-${VORTEX_DEPLOY_PR}" + if [ -n "${VORTEX_DEPLOY_LAGOON_PR:-}" ]; then + deploy_pr_full="pr-${VORTEX_DEPLOY_LAGOON_PR}" # Discover all available environments to check if this is a fresh deployment # or a re-deployment of the existing environment. @@ -148,7 +160,7 @@ else is_redeploy=0 for name in ${names}; do if [ "${deploy_pr_full}" = "${name}" ]; then - note "Found already deployed environment for PR \"${VORTEX_DEPLOY_PR}\"." + note "Found already deployed environment for PR \"${VORTEX_DEPLOY_LAGOON_PR}\"." is_redeploy=1 break fi @@ -162,21 +174,21 @@ else lagoon update variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope global || true # Override DB during re-deployment. - if [ "${VORTEX_DEPLOY_ACTION}" = "deploy_override_db" ]; then + if [ "${VORTEX_DEPLOY_LAGOON_ACTION}" = "deploy_override_db" ]; then task "Adding a DB import override flag for the current deployment." # To update variable value, we need to remove it and add again. lagoon update variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB --value 1 --scope global || true fi - task "Redeploying environment: project ${LAGOON_PROJECT}, PR: ${VORTEX_DEPLOY_PR}." - deploy_output=$(lagoon deploy pullrequest --number "${VORTEX_DEPLOY_PR}" --base-branch-name "${VORTEX_DEPLOY_PR_BASE_BRANCH}" --base-branch-ref "origin/${VORTEX_DEPLOY_PR_BASE_BRANCH}" --head-branch-name "${VORTEX_DEPLOY_BRANCH}" --head-branch-ref "${VORTEX_DEPLOY_PR_HEAD}" --title "${deploy_pr_full}" 2>&1) || exit_code=$? + task "Redeploying environment: project ${VORTEX_DEPLOY_LAGOON_PROJECT}, PR: ${VORTEX_DEPLOY_LAGOON_PR}." + deploy_output=$(lagoon deploy pullrequest --number "${VORTEX_DEPLOY_LAGOON_PR}" --base-branch-name "${VORTEX_DEPLOY_LAGOON_PR_BASE_BRANCH}" --base-branch-ref "origin/${VORTEX_DEPLOY_LAGOON_PR_BASE_BRANCH}" --head-branch-name "${VORTEX_DEPLOY_LAGOON_BRANCH}" --head-branch-ref "${VORTEX_DEPLOY_LAGOON_PR_HEAD}" --title "${deploy_pr_full}" 2>&1) || exit_code=$? exit_code=${exit_code:-0} if is_lagoon_env_limit_exceeded "${deploy_output}"; then note "Lagoon environment limit exceeded." [ "${VORTEX_DEPLOY_LAGOON_FAIL_ENV_LIMIT_EXCEEDED}" = "0" ] && exit_code=0 fi - if [ "${VORTEX_DEPLOY_ACTION:-}" = "deploy_override_db" ]; then + if [ "${VORTEX_DEPLOY_LAGOON_ACTION:-}" = "deploy_override_db" ]; then task "Waiting for deployment to be queued." sleep 10 @@ -188,8 +200,8 @@ else # Deployment of the fresh environment. else # If PR deployments are not configured in Lagoon - it will filter it out and will not deploy. - task "Deploying environment: project ${LAGOON_PROJECT}, PR: ${VORTEX_DEPLOY_PR}." - deploy_output=$(lagoon deploy pullrequest --number "${VORTEX_DEPLOY_PR}" --base-branch-name "${VORTEX_DEPLOY_PR_BASE_BRANCH}" --base-branch-ref "origin/${VORTEX_DEPLOY_PR_BASE_BRANCH}" --head-branch-name "${VORTEX_DEPLOY_BRANCH}" --head-branch-ref "${VORTEX_DEPLOY_PR_HEAD}" --title "${deploy_pr_full}" 2>&1) || exit_code=$? + task "Deploying environment: project ${VORTEX_DEPLOY_LAGOON_PROJECT}, PR: ${VORTEX_DEPLOY_LAGOON_PR}." + deploy_output=$(lagoon deploy pullrequest --number "${VORTEX_DEPLOY_LAGOON_PR}" --base-branch-name "${VORTEX_DEPLOY_LAGOON_PR_BASE_BRANCH}" --base-branch-ref "origin/${VORTEX_DEPLOY_LAGOON_PR_BASE_BRANCH}" --head-branch-name "${VORTEX_DEPLOY_LAGOON_BRANCH}" --head-branch-ref "${VORTEX_DEPLOY_LAGOON_PR_HEAD}" --title "${deploy_pr_full}" 2>&1) || exit_code=$? exit_code=${exit_code:-0} if is_lagoon_env_limit_exceeded "${deploy_output}"; then note "Lagoon environment limit exceeded." @@ -207,8 +219,8 @@ else is_redeploy=0 for name in ${names}; do - if [ "${VORTEX_DEPLOY_BRANCH:-}" = "${name}" ]; then - note "Found already deployed environment for branch \"${VORTEX_DEPLOY_BRANCH}\"." + if [ "${VORTEX_DEPLOY_LAGOON_BRANCH:-}" = "${name}" ]; then + note "Found already deployed environment for branch \"${VORTEX_DEPLOY_LAGOON_BRANCH}\"." is_redeploy=1 break fi @@ -219,37 +231,37 @@ else # Explicitly set DB overwrite flag to 0 due to a bug in Lagoon. # @see https://github.com/uselagoon/lagoon/issues/1922 task "Setting a DB overwrite flag to 0." - lagoon update variable --environment "${VORTEX_DEPLOY_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope global || true + lagoon update variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope global || true # Override DB during re-deployment. - if [ "${VORTEX_DEPLOY_ACTION:-}" = "deploy_override_db" ]; then + if [ "${VORTEX_DEPLOY_LAGOON_ACTION:-}" = "deploy_override_db" ]; then task "Adding a DB import override flag for the current deployment." # To update variable value, we need to remove it and add again. - lagoon update variable --environment "${VORTEX_DEPLOY_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value 1 --scope global || true + lagoon update variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value 1 --scope global || true fi - task "Redeploying environment: project ${LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_BRANCH}." - deploy_output=$(lagoon deploy latest --environment "${VORTEX_DEPLOY_BRANCH}" 2>&1) || exit_code=$? + task "Redeploying environment: project ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}." + deploy_output=$(lagoon deploy latest --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" 2>&1) || exit_code=$? exit_code=${exit_code:-0} if is_lagoon_env_limit_exceeded "${deploy_output}"; then note "Lagoon environment limit exceeded." [ "${VORTEX_DEPLOY_LAGOON_FAIL_ENV_LIMIT_EXCEEDED}" = "0" ] && exit_code=0 fi - if [ "${VORTEX_DEPLOY_ACTION:-}" = "deploy_override_db" ]; then + if [ "${VORTEX_DEPLOY_LAGOON_ACTION:-}" = "deploy_override_db" ]; then task "Waiting for deployment to be queued." sleep 10 task "Removing a DB import override flag for the current deployment." # Note that a variable will be read by Lagoon during queuing of the build. - lagoon update variable --environment "${VORTEX_DEPLOY_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope global || true + lagoon update variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope global || true fi # Deployment of the fresh environment. else # If current branch deployments does not match a regex in Lagoon - it will filter it out and will not deploy. - task "Deploying environment: project ${LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_BRANCH}." - deploy_output=$(lagoon deploy branch --branch "${VORTEX_DEPLOY_BRANCH}" 2>&1) || exit_code=$? + task "Deploying environment: project ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}." + deploy_output=$(lagoon deploy branch --branch "${VORTEX_DEPLOY_LAGOON_BRANCH}" 2>&1) || exit_code=$? exit_code=${exit_code:-0} if is_lagoon_env_limit_exceeded "${deploy_output}"; then note "Lagoon environment limit exceeded." diff --git a/scripts/vortex/download-db-acquia.sh b/scripts/vortex/download-db-acquia.sh index fe208e61..218896b0 100755 --- a/scripts/vortex/download-db-acquia.sh +++ b/scripts/vortex/download-db-acquia.sh @@ -22,38 +22,42 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t +_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" +VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" +for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done + set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # Acquia Cloud API key. -VORTEX_ACQUIA_KEY="${VORTEX_ACQUIA_KEY:-}" +VORTEX_DOWNLOAD_DB_ACQUIA_KEY="${VORTEX_DOWNLOAD_DB_ACQUIA_KEY:-${VORTEX_ACQUIA_KEY:-}}" # Acquia Cloud API secret. -VORTEX_ACQUIA_SECRET="${VORTEX_ACQUIA_SECRET:-}" +VORTEX_DOWNLOAD_DB_ACQUIA_SECRET="${VORTEX_DOWNLOAD_DB_ACQUIA_SECRET:-${VORTEX_ACQUIA_SECRET:-}}" # Application name. Used to discover UUID. -VORTEX_ACQUIA_APP_NAME="${VORTEX_ACQUIA_APP_NAME:-}" +VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME="${VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME:-${VORTEX_ACQUIA_APP_NAME:-}}" # Source environment name used to download the database dump from. -VORTEX_DB_DOWNLOAD_ENVIRONMENT="${VORTEX_DB_DOWNLOAD_ENVIRONMENT:-}" +VORTEX_DOWNLOAD_DB_ENVIRONMENT="${VORTEX_DOWNLOAD_DB_ENVIRONMENT:-}" # Database name within source environment used to download the database dump. -VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME="${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME:-}" +VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME="${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME:-}" # Directory where DB dumps are stored. -VORTEX_DB_DIR="${VORTEX_DB_DIR:-./.data}" +VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR="${VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" # Database dump file name. -VORTEX_DB_FILE="${VORTEX_DB_FILE:-db.sql}" +VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE="${VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE:-${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}}" # Flag to download a fresh copy of the database by triggering a new backup. -VORTEX_DB_DOWNLOAD_FRESH="${VORTEX_DB_DOWNLOAD_FRESH:-}" +VORTEX_DOWNLOAD_DB_FRESH="${VORTEX_DOWNLOAD_DB_FRESH:-}" # Interval in seconds to wait between backup status checks. -VORTEX_DB_DOWNLOAD_ACQUIA_BACKUP_WAIT_INTERVAL="${VORTEX_DB_DOWNLOAD_ACQUIA_BACKUP_WAIT_INTERVAL:-10}" +VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_WAIT_INTERVAL="${VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_WAIT_INTERVAL:-10}" # Maximum time in seconds to wait for backup completion. -VORTEX_DB_DOWNLOAD_ACQUIA_BACKUP_MAX_WAIT="${VORTEX_DB_DOWNLOAD_ACQUIA_BACKUP_MAX_WAIT:-600}" +VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_MAX_WAIT="${VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_MAX_WAIT:-600}" #------------------------------------------------------------------------------- @@ -89,21 +93,21 @@ extract_json_value() { } # Check that all required variables are present. -[ -z "${VORTEX_ACQUIA_KEY}" ] && fail "Missing value for VORTEX_ACQUIA_KEY." && exit 1 -[ -z "${VORTEX_ACQUIA_SECRET}" ] && fail "Missing value for VORTEX_ACQUIA_SECRET." && exit 1 -[ -z "${VORTEX_ACQUIA_APP_NAME}" ] && fail "Missing value for VORTEX_ACQUIA_APP_NAME." && exit 1 -[ -z "${VORTEX_DB_DOWNLOAD_ENVIRONMENT}" ] && fail "Missing value for VORTEX_DB_DOWNLOAD_ENVIRONMENT." && exit 1 -[ -z "${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}" ] && fail "Missing value for VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_ACQUIA_KEY}" ] && fail "Missing value for VORTEX_DOWNLOAD_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_ACQUIA_SECRET}" ] && fail "Missing value for VORTEX_DOWNLOAD_DB_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME}" ] && fail "Missing value for VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME or VORTEX_ACQUIA_APP_NAME." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_ENVIRONMENT}" ] && fail "Missing value for VORTEX_DOWNLOAD_DB_ENVIRONMENT." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}" ] && fail "Missing value for VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME." && exit 1 -mkdir -p "${VORTEX_DB_DIR}" +mkdir -p "${VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR}" task "Retrieving authentication token." -token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") +token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_DOWNLOAD_DB_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_DOWNLOAD_DB_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") [ "${VORTEX_DEBUG-}" = "1" ] && note "Token API response: ${token_json}" # Check for HTTP errors in response if echo "${token_json}" | grep -q '"error"'; then - fail "Authentication failed. Check VORTEX_ACQUIA_KEY and VORTEX_ACQUIA_SECRET. API response: ${token_json}" + fail "Authentication failed. Check VORTEX_DOWNLOAD_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_DOWNLOAD_DB_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET. API response: ${token_json}" exit 1 fi @@ -111,45 +115,45 @@ token="$(echo "${token_json}" | extract_json_value "access_token")" [ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted token: ${token}" [ -z "${token}" ] && fail "Unable to retrieve a token. API response: ${token_json}" && exit 1 -task "Retrieving ${VORTEX_ACQUIA_APP_NAME} application UUID." -app_uuid_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications?filter=name%3D${VORTEX_ACQUIA_APP_NAME/ /%20}") +task "Retrieving ${VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME} application UUID." +app_uuid_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications?filter=name%3D${VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME/ /%20}") [ "${VORTEX_DEBUG-}" = "1" ] && note "Application API response: ${app_uuid_json}" # Check for empty items array (application not found) if echo "${app_uuid_json}" | grep -q '"items":\[\]'; then - fail "Application '${VORTEX_ACQUIA_APP_NAME}' not found. Check application name and access permissions." + fail "Application '${VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME}' not found. Check application name and access permissions." exit 1 fi app_uuid=$(echo "${app_uuid_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "uuid") [ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted app UUID: ${app_uuid}" -[ -z "${app_uuid}" ] && fail "Unable to retrieve an application UUID for '${VORTEX_ACQUIA_APP_NAME}'. API response: ${app_uuid_json}" && exit 1 +[ -z "${app_uuid}" ] && fail "Unable to retrieve an application UUID for '${VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME}'. API response: ${app_uuid_json}" && exit 1 -task "Retrieving ${VORTEX_DB_DOWNLOAD_ENVIRONMENT} environment ID." -envs_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications/${app_uuid}/environments?filter=name%3D${VORTEX_DB_DOWNLOAD_ENVIRONMENT}") +task "Retrieving ${VORTEX_DOWNLOAD_DB_ENVIRONMENT} environment ID." +envs_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications/${app_uuid}/environments?filter=name%3D${VORTEX_DOWNLOAD_DB_ENVIRONMENT}") [ "${VORTEX_DEBUG-}" = "1" ] && note "Environments API response: ${envs_json}" # Check for empty items array (environment not found) if echo "${envs_json}" | grep -q '"items":\[\]'; then - fail "Environment '${VORTEX_DB_DOWNLOAD_ENVIRONMENT}' not found in application '${VORTEX_ACQUIA_APP_NAME}'. Check environment name." + fail "Environment '${VORTEX_DOWNLOAD_DB_ENVIRONMENT}' not found in application '${VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME}'. Check environment name." exit 1 fi env_id=$(echo "${envs_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "id") [ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted environment ID: ${env_id}" -[ -z "${env_id}" ] && fail "Unable to retrieve an environment ID for '${VORTEX_DB_DOWNLOAD_ENVIRONMENT}'. API response: ${envs_json}" && exit 1 +[ -z "${env_id}" ] && fail "Unable to retrieve an environment ID for '${VORTEX_DOWNLOAD_DB_ENVIRONMENT}'. API response: ${envs_json}" && exit 1 # If fresh backup requested, create a new backup and wait for it to complete. -if [ "$VORTEX_DB_DOWNLOAD_FRESH" = "1" ]; then - task "Creating new database backup for ${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}." +if [ "$VORTEX_DOWNLOAD_DB_FRESH" = "1" ]; then + task "Creating new database backup for ${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}." # Trigger backup creation - create_backup_json=$(curl -s -L -X POST -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/environments/${env_id}/databases/${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}/backups") + create_backup_json=$(curl -s -L -X POST -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/environments/${env_id}/databases/${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}/backups") [ "${VORTEX_DEBUG-}" = "1" ] && note "Create backup API response: ${create_backup_json}" # Check for errors if echo "${create_backup_json}" | grep -q '"error"'; then - fail "Failed to create backup for database '${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}'. API response: ${create_backup_json}" + fail "Failed to create backup for database '${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}'. API response: ${create_backup_json}" exit 1 fi @@ -163,8 +167,8 @@ if [ "$VORTEX_DB_DOWNLOAD_FRESH" = "1" ]; then fi task "Waiting for backup to complete." - max_wait="${VORTEX_DB_DOWNLOAD_ACQUIA_BACKUP_MAX_WAIT}" - wait_interval="${VORTEX_DB_DOWNLOAD_ACQUIA_BACKUP_WAIT_INTERVAL}" + max_wait="${VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_MAX_WAIT}" + wait_interval="${VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_WAIT_INTERVAL}" elapsed=0 while [ ${elapsed} -lt "${max_wait}" ]; do @@ -196,31 +200,31 @@ if [ "$VORTEX_DB_DOWNLOAD_FRESH" = "1" ]; then note "Fresh backup will be downloaded." fi -task "Discovering latest backup ID for DB ${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}." -backups_json=$(curl --progress-bar -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/environments/${env_id}/databases/${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}/backups?sort=created") +task "Discovering latest backup ID for DB ${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}." +backups_json=$(curl --progress-bar -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/environments/${env_id}/databases/${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}/backups?sort=created") [ "${VORTEX_DEBUG-}" = "1" ] && note "Backups API response: ${backups_json}" # Check for HTTP errors or database not found if echo "${backups_json}" | grep -q '"error"'; then - fail "Database '${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}' not found in environment '${VORTEX_DB_DOWNLOAD_ENVIRONMENT}'. Check database name. API response: ${backups_json}" + fail "Database '${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}' not found in environment '${VORTEX_DOWNLOAD_DB_ENVIRONMENT}'. Check database name. API response: ${backups_json}" exit 1 fi # Check for empty items array (no backups found) if echo "${backups_json}" | grep -q '"items":\[\]'; then - fail "No backups found for database '${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}' in environment '${VORTEX_DB_DOWNLOAD_ENVIRONMENT}'. Try creating a backup first." + fail "No backups found for database '${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}' in environment '${VORTEX_DOWNLOAD_DB_ENVIRONMENT}'. Try creating a backup first." exit 1 fi # Acquia response has all backups sorted chronologically by created date. backup_id=$(echo "${backups_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "id") [ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted backup ID: ${backup_id}" -[ -z "${backup_id}" ] && fail "Unable to discover backup ID for DB '${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}'. API response: ${backups_json}" && exit 1 +[ -z "${backup_id}" ] && fail "Unable to discover backup ID for DB '${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}'. API response: ${backups_json}" && exit 1 # Insert backup id as a suffix. -file_extension="${VORTEX_DB_FILE##*.}" -file_prefix="${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}_backup_" -file_name="${VORTEX_DB_DIR}/${file_prefix}${backup_id}.${file_extension}" +file_extension="${VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE##*.}" +file_prefix="${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}_backup_" +file_name="${VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR}/${file_prefix}${backup_id}.${file_extension}" file_name_discovered="${file_name}" file_name_compressed="${file_name}.gz" @@ -228,16 +232,16 @@ file_name_compressed="${file_name}.gz" [ "${VORTEX_DEBUG-}" = "1" ] && note "Compressed file name: ${file_name_compressed}" if [ -f "${file_name_discovered}" ]; then - note "Found existing cached DB file \"${file_name_discovered}\" for DB \"${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}\"." + note "Found existing cached DB file \"${file_name_discovered}\" for DB \"${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}\"." else # If the gzipped version exists, then we don't need to re-download it. if [ ! -f "${file_name_compressed}" ]; then - note "Using the latest backup ID ${backup_id} for DB ${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}." + note "Using the latest backup ID ${backup_id} for DB ${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}." - [ ! -d "${VORTEX_DB_DIR:-}" ] && note "Creating dump directory ${VORTEX_DB_DIR}" && mkdir -p "${VORTEX_DB_DIR}" + [ ! -d "${VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR:-}" ] && note "Creating dump directory ${VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR}" && mkdir -p "${VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR}" task "Discovering backup URL." - backup_json=$(curl --progress-bar -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/environments/${env_id}/databases/${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}/backups/${backup_id}/actions/download") + backup_json=$(curl --progress-bar -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/environments/${env_id}/databases/${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}/backups/${backup_id}/actions/download") [ "${VORTEX_DEBUG-}" = "1" ] && note "Backup URL API response: ${backup_json}" backup_url=$(echo "${backup_json}" | extract_json_value "url") [ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted backup URL: ${backup_url}" @@ -248,7 +252,7 @@ else download_result=$? # shellcheck disable=SC2181 - [ "${download_result}" -ne 0 ] && fail "Unable to download database ${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}. curl exit code: ${download_result}" && exit 1 + [ "${download_result}" -ne 0 ] && fail "Unable to download database ${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}. curl exit code: ${download_result}" && exit 1 # Check if the downloaded file exists and has content if [ ! -f "${file_name_compressed}" ] || [ ! -s "${file_name_compressed}" ]; then @@ -259,7 +263,7 @@ else [ "${VORTEX_DEBUG-}" = "1" ] && note "Download completed successfully" else - pass "Found existing cached gzipped DB file ${file_name_compressed} for DB ${VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME}." + pass "Found existing cached gzipped DB file ${file_name_compressed} for DB ${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME}." fi task "Expanding DB file ${file_name_compressed} into ${file_name}." @@ -287,12 +291,12 @@ else [ "${VORTEX_DEBUG-}" = "1" ] && note "Decompression completed successfully" fi -task "Renaming file \"${file_name}\" to \"${VORTEX_DB_DIR}/${VORTEX_DB_FILE}\"." +task "Renaming file \"${file_name}\" to \"${VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR}/${VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE}\"." [ "${VORTEX_DEBUG-}" = "1" ] && note "Moving from: ${file_name}" -[ "${VORTEX_DEBUG-}" = "1" ] && note "Moving to: ${VORTEX_DB_DIR}/${VORTEX_DB_FILE}" -mv "${file_name}" "${VORTEX_DB_DIR}/${VORTEX_DB_FILE}" +[ "${VORTEX_DEBUG-}" = "1" ] && note "Moving to: ${VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR}/${VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE}" +mv "${file_name}" "${VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR}/${VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE}" mv_result=$? -[ "${mv_result}" -ne 0 ] && fail "Unable to rename file from '${file_name}' to '${VORTEX_DB_DIR}/${VORTEX_DB_FILE}'. mv exit code: ${mv_result}" && exit 1 +[ "${mv_result}" -ne 0 ] && fail "Unable to rename file from '${file_name}' to '${VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR}/${VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE}'. mv exit code: ${mv_result}" && exit 1 [ "${VORTEX_DEBUG-}" = "1" ] && note "File rename completed successfully" pass "Finished database dump download from Acquia." diff --git a/scripts/vortex/download-db-container-registry.sh b/scripts/vortex/download-db-container-registry.sh index c096e84c..a6b8a7db 100755 --- a/scripts/vortex/download-db-container-registry.sh +++ b/scripts/vortex/download-db-container-registry.sh @@ -8,11 +8,15 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t +_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" +VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" +for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done + set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # The container image containing database passed in a form of `/`. -VORTEX_DB_IMAGE="${VORTEX_DB_IMAGE:-}" +VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE:-${VORTEX_DB_IMAGE:-}}" # Container registry name. # @@ -25,6 +29,12 @@ VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_USER="${VORTEX_DOWNLOAD_DB_CONTAINER_REGIS # The password to login into the container registry. VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS:-${VORTEX_CONTAINER_REGISTRY_PASS:-}}" +# Directory with database dump file. +VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_DB_DIR="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" + +# The base container image used as a fallback when the archive does not exist. +VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE:-${VORTEX_DB_IMAGE_BASE:-}}" + #------------------------------------------------------------------------------- # @formatter:off @@ -43,47 +53,47 @@ for cmd in docker; do command -v "${cmd}" >/dev/null || { info "Started database data container image download." -[ -z "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY." && exit 1 -[ -z "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_USER}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_USER." && exit 1 -[ -z "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS." && exit 1 -[ -z "${VORTEX_DB_IMAGE}" ] && fail "Destination image name is not specified. Please provide container image name as a first argument to this script in a format /." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY or VORTEX_CONTAINER_REGISTRY." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_USER}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_USER or VORTEX_CONTAINER_REGISTRY_USER." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS or VORTEX_CONTAINER_REGISTRY_PASS." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE}" ] && fail "Destination image name is not specified. Please provide VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE or VORTEX_DB_IMAGE in a format /." && exit 1 -docker image inspect "${VORTEX_DB_IMAGE}" >/dev/null 2>&1 && - note "Found ${VORTEX_DB_IMAGE} image on host." || - note "Not found ${VORTEX_DB_IMAGE} image on host." +docker image inspect "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE}" >/dev/null 2>&1 && + note "Found ${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE} image on host." || + note "Not found ${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE} image on host." image_expanded_successfully=0 -if [ -f "${VORTEX_DB_DIR}/db.tar" ]; then - task "Found archived database container image file ${VORTEX_DB_DIR}/db.tar. Expanding..." +if [ -f "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_DB_DIR}/db.tar" ]; then + task "Found archived database container image file ${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_DB_DIR}/db.tar. Expanding..." # Always use archived image, even if such image already exists on the host. - docker load -q --input "${VORTEX_DB_DIR}/db.tar" + docker load -q --input "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_DB_DIR}/db.tar" # Check that image was expanded and now exists on the host or notify # that it will be downloaded from the registry. - if docker image inspect "${VORTEX_DB_IMAGE}" >/dev/null 2>&1; then - note "Found expanded ${VORTEX_DB_IMAGE} image on host." + if docker image inspect "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE}" >/dev/null 2>&1; then + note "Found expanded ${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE} image on host." image_expanded_successfully=1 else - note "Not found expanded ${VORTEX_DB_IMAGE} image on host." + note "Not found expanded ${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE} image on host." fi fi if [ "${image_expanded_successfully}" -eq 0 ]; then - if [ ! -f "${VORTEX_DB_DIR}/db.tar" ] && [ -n "${VORTEX_DB_IMAGE_BASE:-}" ]; then + if [ ! -f "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_DB_DIR}/db.tar" ] && [ -n "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE:-}" ]; then # If the image archive does not exist and base image was provided - use the # base image which allows "clean slate" for the database. - note "Database container image was not found. Using base image ${VORTEX_DB_IMAGE_BASE}." - export VORTEX_DB_IMAGE="${VORTEX_DB_IMAGE_BASE}" + note "Database container image was not found. Using base image ${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE}." + export VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE}" fi - task "Downloading ${VORTEX_DB_IMAGE} image from the registry." + task "Downloading ${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE} image from the registry." export VORTEX_CONTAINER_REGISTRY="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY}" export VORTEX_CONTAINER_REGISTRY_USER="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_USER}" export VORTEX_CONTAINER_REGISTRY_PASS="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS}" ./scripts/vortex/login-container-registry.sh - docker pull "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY}/${VORTEX_DB_IMAGE}" + docker pull "${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY}/${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE}" fi pass "Finished database data container image download." diff --git a/scripts/vortex/download-db-ftp.sh b/scripts/vortex/download-db-ftp.sh index 64297e24..e9edcc79 100755 --- a/scripts/vortex/download-db-ftp.sh +++ b/scripts/vortex/download-db-ftp.sh @@ -8,29 +8,33 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t +_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" +VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" +for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done + set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # The FTP user. -VORTEX_DB_DOWNLOAD_FTP_USER="${VORTEX_DB_DOWNLOAD_FTP_USER:-}" +VORTEX_DOWNLOAD_DB_FTP_USER="${VORTEX_DOWNLOAD_DB_FTP_USER:-}" # The FTP password. -VORTEX_DB_DOWNLOAD_FTP_PASS="${VORTEX_DB_DOWNLOAD_FTP_PASS:-}" +VORTEX_DOWNLOAD_DB_FTP_PASS="${VORTEX_DOWNLOAD_DB_FTP_PASS:-}" # The FTP host. -VORTEX_DB_DOWNLOAD_FTP_HOST="${VORTEX_DB_DOWNLOAD_FTP_HOST:-}" +VORTEX_DOWNLOAD_DB_FTP_HOST="${VORTEX_DOWNLOAD_DB_FTP_HOST:-}" # The FTP port. -VORTEX_DB_DOWNLOAD_FTP_PORT="${VORTEX_DB_DOWNLOAD_FTP_PORT:-}" +VORTEX_DOWNLOAD_DB_FTP_PORT="${VORTEX_DOWNLOAD_DB_FTP_PORT:-}" # The file name, including any directories. -VORTEX_DB_DOWNLOAD_FTP_FILE="${VORTEX_DB_DOWNLOAD_FTP_FILE:-}" +VORTEX_DOWNLOAD_DB_FTP_FILE="${VORTEX_DOWNLOAD_DB_FTP_FILE:-}" # Directory with database dump file. -VORTEX_DB_DIR="${VORTEX_DB_DIR:-./.data}" +VORTEX_DOWNLOAD_DB_FTP_DB_DIR="${VORTEX_DOWNLOAD_DB_FTP_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" # Database dump file name. -VORTEX_DB_FILE="${VORTEX_DB_FILE:-db.sql}" +VORTEX_DOWNLOAD_DB_FTP_DB_FILE="${VORTEX_DOWNLOAD_DB_FTP_DB_FILE:-${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}}" #------------------------------------------------------------------------------- @@ -49,16 +53,16 @@ for cmd in curl; do command -v "${cmd}" >/dev/null || { }; done # Check all required values. -[ -z "${VORTEX_DB_DOWNLOAD_FTP_USER}" ] && fail "Missing required value for VORTEX_DB_DOWNLOAD_FTP_USER." && exit 1 -[ -z "${VORTEX_DB_DOWNLOAD_FTP_PASS}" ] && fail "Missing required value for VORTEX_DB_DOWNLOAD_FTP_PASS." && exit 1 -[ -z "${VORTEX_DB_DOWNLOAD_FTP_HOST}" ] && fail "Missing required value for VORTEX_DB_DOWNLOAD_FTP_HOST." && exit 1 -[ -z "${VORTEX_DB_DOWNLOAD_FTP_PORT}" ] && fail "Missing required value for VORTEX_DB_DOWNLOAD_FTP_PORT." && exit 1 -[ -z "${VORTEX_DB_DOWNLOAD_FTP_FILE}" ] && fail "Missing required value for VORTEX_DB_DOWNLOAD_FTP_FILE." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_FTP_USER}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_FTP_USER." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_FTP_PASS}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_FTP_PASS." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_FTP_HOST}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_FTP_HOST." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_FTP_PORT}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_FTP_PORT." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_FTP_FILE}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_FTP_FILE." && exit 1 info "Started database dump download from FTP." -mkdir -p "${VORTEX_DB_DIR}" +mkdir -p "${VORTEX_DOWNLOAD_DB_FTP_DB_DIR}" -curl -u "${VORTEX_DB_DOWNLOAD_FTP_USER}":"${VORTEX_DB_DOWNLOAD_FTP_PASS}" "ftp://${VORTEX_DB_DOWNLOAD_FTP_HOST}:${VORTEX_DB_DOWNLOAD_FTP_PORT}/${VORTEX_DB_DOWNLOAD_FTP_FILE}" -o "${VORTEX_DB_DIR}/${VORTEX_DB_FILE}" +curl -u "${VORTEX_DOWNLOAD_DB_FTP_USER}":"${VORTEX_DOWNLOAD_DB_FTP_PASS}" "ftp://${VORTEX_DOWNLOAD_DB_FTP_HOST}:${VORTEX_DOWNLOAD_DB_FTP_PORT}/${VORTEX_DOWNLOAD_DB_FTP_FILE}" -o "${VORTEX_DOWNLOAD_DB_FTP_DB_DIR}/${VORTEX_DOWNLOAD_DB_FTP_DB_FILE}" pass "Finished database dump download from FTP." diff --git a/scripts/vortex/download-db-lagoon.sh b/scripts/vortex/download-db-lagoon.sh index ca69ddcb..bf627cd8 100755 --- a/scripts/vortex/download-db-lagoon.sh +++ b/scripts/vortex/download-db-lagoon.sh @@ -19,50 +19,54 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t +_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" +VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" +for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done + set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # Flag to download a fresh copy of the database. -VORTEX_DB_DOWNLOAD_FRESH="${VORTEX_DB_DOWNLOAD_FRESH:-}" +VORTEX_DOWNLOAD_DB_FRESH="${VORTEX_DOWNLOAD_DB_FRESH:-}" # Lagoon project name. -LAGOON_PROJECT="${LAGOON_PROJECT:?Missing required environment variable LAGOON_PROJECT.}" +VORTEX_DOWNLOAD_DB_LAGOON_PROJECT="${VORTEX_DOWNLOAD_DB_LAGOON_PROJECT:-${LAGOON_PROJECT:?Missing required environment variable LAGOON_PROJECT.}}" # The source environment branch for the database source. -VORTEX_DB_DOWNLOAD_ENVIRONMENT="${VORTEX_DB_DOWNLOAD_ENVIRONMENT:-main}" +VORTEX_DOWNLOAD_DB_ENVIRONMENT="${VORTEX_DOWNLOAD_DB_ENVIRONMENT:-main}" # Remote DB dump directory location. -VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_DIR="/tmp" +VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_DIR="/tmp" # Remote DB dump file name. Cached by the date suffix. -VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE="${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE:-db_$(date +%Y%m%d).sql}" +VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE="${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE:-db_$(date +%Y%m%d).sql}" # Wildcard file name to cleanup previously created dump files. # -# Cleanup runs only if the variable is set and $VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE +# Cleanup runs only if the variable is set and $VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE # does not exist. -VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE_CLEANUP="${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE_CLEANUP:-db_*.sql}" +VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE_CLEANUP="${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE_CLEANUP:-db_*.sql}" # SSH key fingerprint used to connect to a remote. -VORTEX_DB_DOWNLOAD_SSH_FINGERPRINT="${VORTEX_DB_DOWNLOAD_SSH_FINGERPRINT:-}" +VORTEX_DOWNLOAD_DB_SSH_FINGERPRINT="${VORTEX_DOWNLOAD_DB_SSH_FINGERPRINT:-}" # Default SSH file used if custom fingerprint is not provided. -VORTEX_DB_DOWNLOAD_SSH_FILE="${VORTEX_DB_DOWNLOAD_SSH_FILE:-${HOME}/.ssh/id_rsa}" +VORTEX_DOWNLOAD_DB_SSH_FILE="${VORTEX_DOWNLOAD_DB_SSH_FILE:-${HOME}/.ssh/id_rsa}" # The SSH host of the Lagoon environment. -VORTEX_DB_DOWNLOAD_LAGOON_SSH_HOST="${VORTEX_DB_DOWNLOAD_LAGOON_SSH_HOST:-ssh.lagoon.amazeeio.cloud}" +VORTEX_DOWNLOAD_DB_LAGOON_SSH_HOST="${VORTEX_DOWNLOAD_DB_LAGOON_SSH_HOST:-ssh.lagoon.amazeeio.cloud}" # The SSH port of the Lagoon environment. -VORTEX_DB_DOWNLOAD_LAGOON_SSH_PORT="${VORTEX_DB_DOWNLOAD_LAGOON_SSH_PORT:-32222}" +VORTEX_DOWNLOAD_DB_LAGOON_SSH_PORT="${VORTEX_DOWNLOAD_DB_LAGOON_SSH_PORT:-32222}" # The SSH user of the Lagoon environment. -VORTEX_DB_DOWNLOAD_LAGOON_SSH_USER="${VORTEX_DB_DOWNLOAD_LAGOON_SSH_USER:-${LAGOON_PROJECT}-${VORTEX_DB_DOWNLOAD_ENVIRONMENT}}" +VORTEX_DOWNLOAD_DB_LAGOON_SSH_USER="${VORTEX_DOWNLOAD_DB_LAGOON_SSH_USER:-${VORTEX_DOWNLOAD_DB_LAGOON_PROJECT}-${VORTEX_DOWNLOAD_DB_ENVIRONMENT}}" # Directory where DB dumps are stored on the host. -VORTEX_DB_DIR="${VORTEX_DB_DIR:-./.data}" +VORTEX_DOWNLOAD_DB_LAGOON_DB_DIR="${VORTEX_DOWNLOAD_DB_LAGOON_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" # Database dump file name on the host. -VORTEX_DB_FILE="${VORTEX_DB_FILE:-db.sql}" +VORTEX_DOWNLOAD_DB_LAGOON_DB_FILE="${VORTEX_DOWNLOAD_DB_LAGOON_DB_FILE:-${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}}" # Name of the webroot directory with Drupal codebase. WEBROOT="${WEBROOT:-web}" @@ -84,23 +88,23 @@ for cmd in ssh rsync; do command -v "${cmd}" >/dev/null || { info "Started database dump download from Lagoon." -export VORTEX_SSH_PREFIX="DB_DOWNLOAD" && . ./scripts/vortex/setup-ssh.sh +export VORTEX_SSH_PREFIX="DOWNLOAD_DB" && . ./scripts/vortex/setup-ssh.sh ssh_opts=(-o "UserKnownHostsFile=/dev/null") ssh_opts+=(-o "StrictHostKeyChecking=no") ssh_opts+=(-o "LogLevel=error") ssh_opts+=(-o "IdentitiesOnly=yes") -ssh_opts+=(-p "${VORTEX_DB_DOWNLOAD_LAGOON_SSH_PORT}") -if [ "${VORTEX_DB_DOWNLOAD_SSH_FILE:-}" != false ]; then - ssh_opts+=(-i "${VORTEX_DB_DOWNLOAD_SSH_FILE}") +ssh_opts+=(-p "${VORTEX_DOWNLOAD_DB_LAGOON_SSH_PORT}") +if [ "${VORTEX_DOWNLOAD_DB_SSH_FILE:-}" != false ]; then + ssh_opts+=(-i "${VORTEX_DOWNLOAD_DB_SSH_FILE}") fi -if [ ! -d "${VORTEX_DB_DIR}" ]; then +if [ ! -d "${VORTEX_DOWNLOAD_DB_LAGOON_DB_DIR}" ]; then task "Creating directory for database dumps." - mkdir -p "${VORTEX_DB_DIR}" + mkdir -p "${VORTEX_DOWNLOAD_DB_LAGOON_DB_DIR}" fi -if [ "$VORTEX_DB_DOWNLOAD_FRESH" = "1" ]; then +if [ "$VORTEX_DOWNLOAD_DB_FRESH" = "1" ]; then note "Database dump refresh requested. Will create a new dump." fi @@ -114,18 +118,18 @@ fi task "Discovering or creating a database dump on Lagoon." ssh \ "${ssh_opts[@]}" \ - "${VORTEX_DB_DOWNLOAD_LAGOON_SSH_USER}@${VORTEX_DB_DOWNLOAD_LAGOON_SSH_HOST}" service=cli container=cli \ - "if [ ! -f \"${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_DIR}/${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE}\" ] || [ \"${VORTEX_DB_DOWNLOAD_FRESH}\" = \"1\" ] ; then \ - [ -n \"${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE_CLEANUP}\" ] && rm -f \"${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_DIR}\"\/${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE_CLEANUP} && echo \"Removed previously created DB dumps.\"; \ - echo \" > Creating a database dump ${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_DIR}/${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE}.\"; \ - /app/vendor/bin/drush --root=./${WEBROOT} sql:dump --structure-tables-key=common --structure-tables-list=ban,event_log_track,flood,login_security_track,purge_queue,queue,webform_submission,webform_submission_data,webform_submission_log,watchdog,cache* --extra-dump='--disable-ssl --no-tablespaces' > \"${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_DIR}/${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE}\"; \ + "${VORTEX_DOWNLOAD_DB_LAGOON_SSH_USER}@${VORTEX_DOWNLOAD_DB_LAGOON_SSH_HOST}" service=cli container=cli \ + "if [ ! -f \"${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_DIR}/${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE}\" ] || [ \"${VORTEX_DOWNLOAD_DB_FRESH}\" = \"1\" ] ; then \ + [ -n \"${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE_CLEANUP}\" ] && rm -f \"${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_DIR}\"\/${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE_CLEANUP} && echo \"Removed previously created DB dumps.\"; \ + echo \" > Creating a database dump ${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_DIR}/${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE}.\"; \ + /app/vendor/bin/drush --root=./${WEBROOT} sql:dump --structure-tables-key=common --structure-tables-list=ban,event_log_track,flood,login_security_track,purge_queue,queue,webform_submission,webform_submission_data,webform_submission_log,watchdog,cache* --extra-dump='--disable-ssl --no-tablespaces' > \"${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_DIR}/${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE}\"; \ else \ - echo \" > Using existing dump ${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_DIR}/${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE}.\"; \ + echo \" > Using existing dump ${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_DIR}/${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE}.\"; \ fi" task "Downloading a database dump." ssh_opts_string="${ssh_opts[@]}" rsync_opts=(-e "ssh ${ssh_opts_string}") -rsync "${rsync_opts[@]}" "${VORTEX_DB_DOWNLOAD_LAGOON_SSH_USER}@${VORTEX_DB_DOWNLOAD_LAGOON_SSH_HOST}":"${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_DIR}"/"${VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE}" "${VORTEX_DB_DIR}/${VORTEX_DB_FILE}" +rsync "${rsync_opts[@]}" "${VORTEX_DOWNLOAD_DB_LAGOON_SSH_USER}@${VORTEX_DOWNLOAD_DB_LAGOON_SSH_HOST}":"${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_DIR}"/"${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE}" "${VORTEX_DOWNLOAD_DB_LAGOON_DB_DIR}/${VORTEX_DOWNLOAD_DB_LAGOON_DB_FILE}" pass "Finished database dump download from Lagoon." diff --git a/scripts/vortex/download-db-s3.sh b/scripts/vortex/download-db-s3.sh new file mode 100755 index 00000000..a503bb42 --- /dev/null +++ b/scripts/vortex/download-db-s3.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +## +# Download DB dump from S3. +# +# Uses AWS Signature Version 4 with curl (no AWS CLI required). +# +# IMPORTANT! This script runs outside the container on the host system. +# +# shellcheck disable=SC1090,SC1091 + +t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t + +_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" +VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" +for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done + +set -eu +[ "${VORTEX_DEBUG-}" = "1" ] && set -x + +# AWS access key. +VORTEX_DOWNLOAD_DB_S3_ACCESS_KEY="${VORTEX_DOWNLOAD_DB_S3_ACCESS_KEY:-${S3_ACCESS_KEY:-}}" + +# AWS secret key. +VORTEX_DOWNLOAD_DB_S3_SECRET_KEY="${VORTEX_DOWNLOAD_DB_S3_SECRET_KEY:-${S3_SECRET_KEY:-}}" + +# S3 bucket name. +VORTEX_DOWNLOAD_DB_S3_BUCKET="${VORTEX_DOWNLOAD_DB_S3_BUCKET:-${S3_BUCKET:-}}" + +# S3 region. +VORTEX_DOWNLOAD_DB_S3_REGION="${VORTEX_DOWNLOAD_DB_S3_REGION:-${S3_REGION:-}}" + +# S3 prefix (path within the bucket). +VORTEX_DOWNLOAD_DB_S3_PREFIX="${VORTEX_DOWNLOAD_DB_S3_PREFIX:-${S3_PREFIX:-}}" + +# Directory with database dump file. +VORTEX_DOWNLOAD_DB_S3_DB_DIR="${VORTEX_DOWNLOAD_DB_S3_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" + +# Database dump file name. +VORTEX_DOWNLOAD_DB_S3_DB_FILE="${VORTEX_DOWNLOAD_DB_S3_DB_FILE:-${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}}" + +# ------------------------------------------------------------------------------ + +# @formatter:off +note() { printf " %s\n" "${1}"; } +task() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[TASK] %s\033[0m\n" "${1}" || printf "[TASK] %s\n" "${1}"; } +info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[36m[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 + +for cmd in curl openssl; do command -v "${cmd}" >/dev/null || { + fail "Command ${cmd} is not available" + exit 1 +}; done + +[ -z "${VORTEX_DOWNLOAD_DB_S3_ACCESS_KEY}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_S3_ACCESS_KEY." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_S3_SECRET_KEY}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_S3_SECRET_KEY." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_S3_BUCKET}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_S3_BUCKET." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_S3_REGION}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_S3_REGION." && exit 1 + +info "Started database dump download from S3." + +# Ensure prefix ends with a trailing slash if non-empty. +[ -n "${VORTEX_DOWNLOAD_DB_S3_PREFIX}" ] && VORTEX_DOWNLOAD_DB_S3_PREFIX="${VORTEX_DOWNLOAD_DB_S3_PREFIX%/}/" + +mkdir -p "${VORTEX_DOWNLOAD_DB_S3_DB_DIR}" + +request_type="GET" +auth_type="AWS4-HMAC-SHA256" +service="s3" +content_type="application/octet-stream" + +host="${service}.${VORTEX_DOWNLOAD_DB_S3_REGION}.amazonaws.com" +uri="/${VORTEX_DOWNLOAD_DB_S3_BUCKET}/${VORTEX_DOWNLOAD_DB_S3_PREFIX}${VORTEX_DOWNLOAD_DB_S3_DB_FILE}" +object_url="https://${host}${uri}" +date_short="$(date -u '+%Y%m%d')" +date_long="${date_short}T$(date -u '+%H%M%S')Z" + +note "Remote file: ${VORTEX_DOWNLOAD_DB_S3_PREFIX}${VORTEX_DOWNLOAD_DB_S3_DB_FILE}" +note "Local path: ${VORTEX_DOWNLOAD_DB_S3_DB_DIR}/${VORTEX_DOWNLOAD_DB_S3_DB_FILE}" +note "S3 bucket: ${VORTEX_DOWNLOAD_DB_S3_BUCKET}" +note "S3 region: ${VORTEX_DOWNLOAD_DB_S3_REGION}" +[ -n "${VORTEX_DOWNLOAD_DB_S3_PREFIX}" ] && note "S3 prefix: ${VORTEX_DOWNLOAD_DB_S3_PREFIX}" + +# shellcheck disable=SC2059 +hash_sha256() { printf "${1}" | openssl dgst -sha256 | sed 's/^.* //'; } +# shellcheck disable=SC2059 +hmac_sha256() { printf "${2}" | openssl dgst -sha256 -mac HMAC -macopt "${1}" | sed 's/^.* //'; } + +payload_hash="$(printf "" | openssl dgst -sha256 | sed 's/^.* //')" + +headers="content-type:${content_type} +host:${host} +x-amz-content-sha256:${payload_hash} +x-amz-date:${date_long}" + +signed_headers="content-type;host;x-amz-content-sha256;x-amz-date" +request="${request_type} +${uri}\n +${headers}\n +${signed_headers} +${payload_hash}" + +# Create the signature. +# shellcheck disable=SC2059 +create_signature() { + string_to_sign="${auth_type}\n${date_long}\n${date_short}/${VORTEX_DOWNLOAD_DB_S3_REGION}/${service}/aws4_request\n$(hash_sha256 "${request}")" + date_key=$(hmac_sha256 key:"AWS4${VORTEX_DOWNLOAD_DB_S3_SECRET_KEY}" "${date_short}") + region_key=$(hmac_sha256 hexkey:"${date_key}" "${VORTEX_DOWNLOAD_DB_S3_REGION}") + service_key=$(hmac_sha256 hexkey:"${region_key}" "${service}") + signing_key=$(hmac_sha256 hexkey:"${service_key}" "aws4_request") + + printf "${string_to_sign}" | openssl dgst -sha256 -mac HMAC -macopt hexkey:"${signing_key}" | sed 's/(stdin)= //' | sed 's/SHA2-256//' +} + +signature="$(create_signature)" +auth_header="\ +${auth_type} Credential=${VORTEX_DOWNLOAD_DB_S3_ACCESS_KEY}/${date_short}/\ +${VORTEX_DOWNLOAD_DB_S3_REGION}/${service}/aws4_request, \ +SignedHeaders=${signed_headers}, Signature=${signature}" + +curl "${object_url}" \ + -H "Authorization: ${auth_header}" \ + -H "content-type: ${content_type}" \ + -H "x-amz-content-sha256: ${payload_hash}" \ + -H "x-amz-date: ${date_long}" \ + -f -S -o "${VORTEX_DOWNLOAD_DB_S3_DB_DIR}/${VORTEX_DOWNLOAD_DB_S3_DB_FILE}" + +pass "Finished database dump download from S3." diff --git a/scripts/vortex/download-db-url.sh b/scripts/vortex/download-db-url.sh index 63b44150..1f3d13a8 100755 --- a/scripts/vortex/download-db-url.sh +++ b/scripts/vortex/download-db-url.sh @@ -8,21 +8,25 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t +_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" +VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" +for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done + set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # URL of the remote database. If HTTP authentication required, it must be # included in the variable. -VORTEX_DB_DOWNLOAD_URL="${VORTEX_DB_DOWNLOAD_URL:-}" +VORTEX_DOWNLOAD_DB_URL="${VORTEX_DOWNLOAD_DB_URL:-}" # Directory with database dump file. -VORTEX_DB_DIR="${VORTEX_DB_DIR:-./.data}" +VORTEX_DOWNLOAD_DB_URL_DB_DIR="${VORTEX_DOWNLOAD_DB_URL_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" # Database dump file name. -VORTEX_DB_FILE="${VORTEX_DB_FILE:-db.sql}" +VORTEX_DOWNLOAD_DB_URL_DB_FILE="${VORTEX_DOWNLOAD_DB_URL_DB_FILE:-${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}}" # Password for unzipping password-protected zip files. -VORTEX_DB_DOWNLOAD_UNZIP_PASSWORD="${VORTEX_DB_DOWNLOAD_UNZIP_PASSWORD:-}" +VORTEX_DOWNLOAD_DB_UNZIP_PASSWORD="${VORTEX_DOWNLOAD_DB_UNZIP_PASSWORD:-}" #------------------------------------------------------------------------------- @@ -42,27 +46,27 @@ for cmd in curl unzip; do command -v "${cmd}" >/dev/null || { info "Started database dump download from URL." # Check all required values. -[ -z "${VORTEX_DB_DOWNLOAD_URL}" ] && fail "Missing required value for VORTEX_DB_DOWNLOAD_URL." && exit 1 +[ -z "${VORTEX_DOWNLOAD_DB_URL}" ] && fail "Missing required value for VORTEX_DOWNLOAD_DB_URL." && exit 1 -mkdir -p "${VORTEX_DB_DIR}" +mkdir -p "${VORTEX_DOWNLOAD_DB_URL_DB_DIR}" note "Downloading database dump file." -curl -Ls "${VORTEX_DB_DOWNLOAD_URL}" -o "${VORTEX_DB_DIR}/${VORTEX_DB_FILE}" +curl -Ls "${VORTEX_DOWNLOAD_DB_URL}" -o "${VORTEX_DOWNLOAD_DB_URL_DB_DIR}/${VORTEX_DOWNLOAD_DB_URL_DB_FILE}" -if [ "${VORTEX_DB_DOWNLOAD_URL%*.zip}" != "${VORTEX_DB_DOWNLOAD_URL}" ]; then +if [ "${VORTEX_DOWNLOAD_DB_URL%*.zip}" != "${VORTEX_DOWNLOAD_DB_URL}" ]; then note "Detecting zip file, preparing for extraction." - mv "${VORTEX_DB_DIR}/${VORTEX_DB_FILE}" "${VORTEX_DB_DIR}/${VORTEX_DB_FILE}.zip" + mv "${VORTEX_DOWNLOAD_DB_URL_DB_DIR}/${VORTEX_DOWNLOAD_DB_URL_DB_FILE}" "${VORTEX_DOWNLOAD_DB_URL_DB_DIR}/${VORTEX_DOWNLOAD_DB_URL_DB_FILE}.zip" # Create temporary directory for extraction - temp_extract_dir="${VORTEX_DB_DIR}/tmp_extract_$$" + temp_extract_dir="${VORTEX_DOWNLOAD_DB_URL_DB_DIR}/tmp_extract_$$" mkdir -p "${temp_extract_dir}" - if [ -n "${VORTEX_DB_DOWNLOAD_UNZIP_PASSWORD}" ]; then + if [ -n "${VORTEX_DOWNLOAD_DB_UNZIP_PASSWORD}" ]; then note "Unzipping password-protected database dump file." - unzip -o -P "${VORTEX_DB_DOWNLOAD_UNZIP_PASSWORD}" "${VORTEX_DB_DIR}/${VORTEX_DB_FILE}.zip" -d "${temp_extract_dir}" + unzip -o -P "${VORTEX_DOWNLOAD_DB_UNZIP_PASSWORD}" "${VORTEX_DOWNLOAD_DB_URL_DB_DIR}/${VORTEX_DOWNLOAD_DB_URL_DB_FILE}.zip" -d "${temp_extract_dir}" else note "Unzipping database dump file." - unzip -o "${VORTEX_DB_DIR}/${VORTEX_DB_FILE}.zip" -d "${temp_extract_dir}" + unzip -o "${VORTEX_DOWNLOAD_DB_URL_DB_DIR}/${VORTEX_DOWNLOAD_DB_URL_DB_FILE}.zip" -d "${temp_extract_dir}" fi # Find the first regular file (not directory) in the extracted content. @@ -72,16 +76,16 @@ if [ "${VORTEX_DB_DOWNLOAD_URL%*.zip}" != "${VORTEX_DB_DOWNLOAD_URL}" ]; then if [ -z "${extracted_file}" ]; then fail "No files found in the zip archive." rm -rf "${temp_extract_dir}" >/dev/null - rm -f "${VORTEX_DB_DIR}/${VORTEX_DB_FILE}.zip" >/dev/null + rm -f "${VORTEX_DOWNLOAD_DB_URL_DB_DIR}/${VORTEX_DOWNLOAD_DB_URL_DB_FILE}.zip" >/dev/null exit 1 fi note "Moving extracted file to target location." - mv "${extracted_file}" "${VORTEX_DB_DIR}/${VORTEX_DB_FILE}" + mv "${extracted_file}" "${VORTEX_DOWNLOAD_DB_URL_DB_DIR}/${VORTEX_DOWNLOAD_DB_URL_DB_FILE}" note "Cleaning up temporary files." rm -rf "${temp_extract_dir}" >/dev/null - rm -f "${VORTEX_DB_DIR}/${VORTEX_DB_FILE}.zip" >/dev/null + rm -f "${VORTEX_DOWNLOAD_DB_URL_DB_DIR}/${VORTEX_DOWNLOAD_DB_URL_DB_FILE}.zip" >/dev/null fi pass "Finished database dump download from URL." diff --git a/scripts/vortex/download-db.sh b/scripts/vortex/download-db.sh index abf9e12e..d770d2a2 100755 --- a/scripts/vortex/download-db.sh +++ b/scripts/vortex/download-db.sh @@ -10,19 +10,29 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t +_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" +VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" +for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done + set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # Note that `container_registry` works only for database-in-image # database storage (when $VORTEX_DB_IMAGE variable has a value). -VORTEX_DB_DOWNLOAD_SOURCE="${VORTEX_DB_DOWNLOAD_SOURCE:-curl}" +VORTEX_DOWNLOAD_DB_SOURCE="${VORTEX_DOWNLOAD_DB_SOURCE:-url}" # Force DB download even if the cache exists. # Usually set in CircleCI UI to override per build cache. -VORTEX_DB_DOWNLOAD_FORCE="${VORTEX_DB_DOWNLOAD_FORCE:-}" +VORTEX_DOWNLOAD_DB_FORCE="${VORTEX_DOWNLOAD_DB_FORCE:-}" # Proceed with download. -VORTEX_DB_DOWNLOAD_PROCEED="${VORTEX_DB_DOWNLOAD_PROCEED:-1}" +VORTEX_DOWNLOAD_DB_PROCEED="${VORTEX_DOWNLOAD_DB_PROCEED:-1}" + +# Database dump file name. +VORTEX_DOWNLOAD_DB_FILE="${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}" + +# Directory with database dump file. +VORTEX_DOWNLOAD_DB_DIR="${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}" # ------------------------------------------------------------------------------ @@ -36,48 +46,52 @@ fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\03 info "Started database download." -[ "${VORTEX_DB_DOWNLOAD_PROCEED}" != "1" ] && pass "Skipping database download as DB_DOWNLOAD_PROCEED is not set to 1." && exit 0 +[ "${VORTEX_DOWNLOAD_DB_PROCEED}" != "1" ] && pass "Skipping database download as DB_DOWNLOAD_PROCEED is not set to 1." && exit 0 -db_file_basename="${VORTEX_DB_FILE%.*}" -[ -d "${VORTEX_DB_DIR:-}" ] && found_db=$(find "${VORTEX_DB_DIR}" -name "${db_file_basename}.sql" -o -name "${db_file_basename}.tar") +db_file_basename="${VORTEX_DOWNLOAD_DB_FILE%.*}" +[ -d "${VORTEX_DOWNLOAD_DB_DIR:-}" ] && found_db=$(find "${VORTEX_DOWNLOAD_DB_DIR}" -name "${db_file_basename}.sql" -o -name "${db_file_basename}.tar") if [ -n "${found_db:-}" ]; then note "Found existing database dump file(s)." - ls -Alh "${VORTEX_DB_DIR}" 2>/dev/null || true + ls -Alh "${VORTEX_DOWNLOAD_DB_DIR}" 2>/dev/null || true - if [ "${VORTEX_DB_DOWNLOAD_FORCE}" != "1" ]; then + if [ "${VORTEX_DOWNLOAD_DB_FORCE}" != "1" ]; then note "Using existing database dump file(s)." note "Download will not proceed." - note "Remove existing database file or set VORTEX_DB_DOWNLOAD_FORCE value to 1 to force download." + note "Remove existing database file or set VORTEX_DOWNLOAD_DB_FORCE value to 1 to force download." exit 0 else note "Will download a fresh copy of the database." fi fi -if [ "${VORTEX_DB_DOWNLOAD_SOURCE}" = "ftp" ]; then +if [ "${VORTEX_DOWNLOAD_DB_SOURCE}" = "ftp" ]; then ./scripts/vortex/download-db-ftp.sh fi -if [ "${VORTEX_DB_DOWNLOAD_SOURCE}" = "url" ]; then +if [ "${VORTEX_DOWNLOAD_DB_SOURCE}" = "url" ]; then ./scripts/vortex/download-db-url.sh fi -if [ "${VORTEX_DB_DOWNLOAD_SOURCE}" = "acquia" ]; then +if [ "${VORTEX_DOWNLOAD_DB_SOURCE}" = "acquia" ]; then ./scripts/vortex/download-db-acquia.sh fi -if [ "${VORTEX_DB_DOWNLOAD_SOURCE}" = "lagoon" ]; then +if [ "${VORTEX_DOWNLOAD_DB_SOURCE}" = "lagoon" ]; then ./scripts/vortex/download-db-lagoon.sh fi -if [ "${VORTEX_DB_DOWNLOAD_SOURCE}" = "container_registry" ]; then +if [ "${VORTEX_DOWNLOAD_DB_SOURCE}" = "container_registry" ]; then ./scripts/vortex/download-db-container-registry.sh fi -ls -Alh "${VORTEX_DB_DIR}" || true +if [ "${VORTEX_DOWNLOAD_DB_SOURCE}" = "s3" ]; then + ./scripts/vortex/download-db-s3.sh +fi + +ls -Alh "${VORTEX_DOWNLOAD_DB_DIR}" || true # Create a semaphore file to indicate that the database has been downloaded. -[ -n "${VORTEX_DB_DOWNLOAD_SEMAPHORE:-}" ] && touch "${VORTEX_DB_DOWNLOAD_SEMAPHORE}" +[ -n "${VORTEX_DOWNLOAD_DB_SEMAPHORE:-}" ] && touch "${VORTEX_DOWNLOAD_DB_SEMAPHORE}" pass "Finished database download." diff --git a/scripts/vortex/export-db-file.sh b/scripts/vortex/export-db-file.sh index bca6f072..c00bd59f 100755 --- a/scripts/vortex/export-db-file.sh +++ b/scripts/vortex/export-db-file.sh @@ -10,7 +10,7 @@ set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # Directory with database dump file. -VORTEX_DB_EXPORT_FILE_DIR="${VORTEX_DB_EXPORT_FILE_DIR:-${VORTEX_DB_DIR:-./.data}}" +VORTEX_EXPORT_DB_FILE_DIR="${VORTEX_EXPORT_DB_FILE_DIR:-${VORTEX_DB_DIR:-./.data}}" # ------------------------------------------------------------------------------ @@ -28,7 +28,7 @@ drush() { ./vendor/bin/drush -y "$@"; } # Create dump file name with a timestamp or use the file name provided # as a first argument. -dump_file=$([ "${1:-}" ] && echo "${VORTEX_DB_EXPORT_FILE_DIR}/${1}" || echo "${VORTEX_DB_EXPORT_FILE_DIR}/export_db_$(date +%Y%m%d_%H%M%S).sql") +dump_file=$([ "${1:-}" ] && echo "${VORTEX_EXPORT_DB_FILE_DIR}/${1}" || echo "${VORTEX_EXPORT_DB_FILE_DIR}/export_db_$(date +%Y%m%d_%H%M%S).sql") # If dump file is relative - update it to the parent directory, because the # `drush sql:dump` command result file is relative to Drupal root, but provided @@ -36,7 +36,7 @@ dump_file=$([ "${1:-}" ] && echo "${VORTEX_DB_EXPORT_FILE_DIR}/${1}" || echo "${ dump_file_drush="${dump_file/#.\//../}" # Create a directory to store database dump. -mkdir -p "${VORTEX_DB_EXPORT_FILE_DIR}" +mkdir -p "${VORTEX_EXPORT_DB_FILE_DIR}" # Dump database into a file. drush sql:dump --skip-tables-key=common --result-file="${dump_file_drush}" -q diff --git a/scripts/vortex/export-db-image.sh b/scripts/vortex/export-db-image.sh index 9bbc7e44..727b029f 100755 --- a/scripts/vortex/export-db-image.sh +++ b/scripts/vortex/export-db-image.sh @@ -12,19 +12,19 @@ set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # Container image archive file name. -VORTEX_DB_EXPORT_IMAGE_ARCHIVE_FILE="${VORTEX_DB_EXPORT_IMAGE_ARCHIVE_FILE:-${1}}" +VORTEX_EXPORT_DB_IMAGE_ARCHIVE_FILE="${VORTEX_EXPORT_DB_IMAGE_ARCHIVE_FILE:-${1}}" # Container image to store in a form of `/`. -VORTEX_DB_EXPORT_IMAGE="${VORTEX_DB_EXPORT_IMAGE:-}" +VORTEX_EXPORT_DB_IMAGE="${VORTEX_EXPORT_DB_IMAGE:-}" # Container registry name. -VORTEX_DB_EXPORT_CONTAINER_REGISTRY="${VORTEX_DB_EXPORT_CONTAINER_REGISTRY:-${VORTEX_CONTAINER_REGISTRY:-docker.io}}" +VORTEX_EXPORT_DB_CONTAINER_REGISTRY="${VORTEX_EXPORT_DB_CONTAINER_REGISTRY:-${VORTEX_CONTAINER_REGISTRY:-docker.io}}" # The service name to capture. -VORTEX_DB_EXPORT_SERVICE_NAME="${VORTEX_DB_EXPORT_SERVICE_NAME:-database}" +VORTEX_EXPORT_DB_SERVICE_NAME="${VORTEX_EXPORT_DB_SERVICE_NAME:-database}" # Directory with database image archive file. -VORTEX_DB_EXPORT_IMAGE_DIR="${VORTEX_DB_EXPORT_IMAGE_DIR:-${VORTEX_DB_DIR}}" +VORTEX_EXPORT_DB_IMAGE_DIR="${VORTEX_EXPORT_DB_IMAGE_DIR:-${VORTEX_DB_DIR}}" # ------------------------------------------------------------------------------ @@ -44,23 +44,23 @@ for cmd in docker; do command -v "${cmd}" >/dev/null || { info "Started database data container image export." -[ -z "${VORTEX_DB_EXPORT_IMAGE}" ] && fail "Destination image name is not specified. Please provide container image as a variable VORTEX_DB_EXPORT_IMAGE in a format /." && exit 1 +[ -z "${VORTEX_EXPORT_DB_IMAGE}" ] && fail "Destination image name is not specified. Please provide container image as a variable VORTEX_EXPORT_DB_IMAGE in a format /." && exit 1 -cid="$(docker compose ps -q "${VORTEX_DB_EXPORT_SERVICE_NAME}")" -note "Found ${VORTEX_DB_EXPORT_SERVICE_NAME} service container with id ${cid}." +cid="$(docker compose ps -q "${VORTEX_EXPORT_DB_SERVICE_NAME}")" +note "Found ${VORTEX_EXPORT_DB_SERVICE_NAME} service container with id ${cid}." -new_image="${VORTEX_DB_EXPORT_CONTAINER_REGISTRY}/${VORTEX_DB_EXPORT_IMAGE}" +new_image="${VORTEX_EXPORT_DB_CONTAINER_REGISTRY}/${VORTEX_EXPORT_DB_IMAGE}" task "Locking and unlocking tables before upgrade." -docker compose exec -T "${VORTEX_DB_EXPORT_SERVICE_NAME}" mysql -e "FLUSH TABLES WITH READ LOCK;" +docker compose exec -T "${VORTEX_EXPORT_DB_SERVICE_NAME}" mysql -e "FLUSH TABLES WITH READ LOCK;" sleep 5 -docker compose exec -T "${VORTEX_DB_EXPORT_SERVICE_NAME}" mysql -e "UNLOCK TABLES;" +docker compose exec -T "${VORTEX_EXPORT_DB_SERVICE_NAME}" mysql -e "UNLOCK TABLES;" task "Running forced service upgrade." -docker compose exec -T "${VORTEX_DB_EXPORT_SERVICE_NAME}" sh -c "mariadb-upgrade --force || mariadb-upgrade --force" +docker compose exec -T "${VORTEX_EXPORT_DB_SERVICE_NAME}" sh -c "mariadb-upgrade --force || mariadb-upgrade --force" task "Locking tables after upgrade." -docker compose exec -T "${VORTEX_DB_EXPORT_SERVICE_NAME}" mysql -e "FLUSH TABLES WITH READ LOCK;" +docker compose exec -T "${VORTEX_EXPORT_DB_SERVICE_NAME}" mysql -e "FLUSH TABLES WITH READ LOCK;" task "Committing exported container image with name ${new_image}." iid=$(docker commit "${cid}" "${new_image}") @@ -68,11 +68,11 @@ iid="${iid#sha256:}" note "Committed exported container image with id ${iid}." # Create directory to store database dump. -mkdir -p "${VORTEX_DB_EXPORT_IMAGE_DIR}" +mkdir -p "${VORTEX_EXPORT_DB_IMAGE_DIR}" # Create dump file name with a timestamp or use the file name provided # as a first argument. Also, make sure that the extension is correct. -archive_file=$([ "${VORTEX_DB_EXPORT_IMAGE_ARCHIVE_FILE}" ] && echo "${VORTEX_DB_EXPORT_IMAGE_DIR}/${VORTEX_DB_EXPORT_IMAGE_ARCHIVE_FILE//.sql/.tar}" || echo "${VORTEX_DB_EXPORT_IMAGE_DIR}/export_db_$(date +%Y%m%d_%H%M%S).tar") +archive_file=$([ "${VORTEX_EXPORT_DB_IMAGE_ARCHIVE_FILE}" ] && echo "${VORTEX_EXPORT_DB_IMAGE_DIR}/${VORTEX_EXPORT_DB_IMAGE_ARCHIVE_FILE//.sql/.tar}" || echo "${VORTEX_EXPORT_DB_IMAGE_DIR}/export_db_$(date +%Y%m%d_%H%M%S).tar") task "Exporting database image archive to file ${archive_file}." diff --git a/scripts/vortex/export-db.sh b/scripts/vortex/export-db.sh index a66279b3..d5b46727 100755 --- a/scripts/vortex/export-db.sh +++ b/scripts/vortex/export-db.sh @@ -16,7 +16,7 @@ set -eu # Name of the database container image to use. Uncomment to use an image with # a DB data loaded into it. # @see https://github.com/drevops/mariadb-drupal-data to seed your DB image. -VORTEX_DB_IMAGE="${VORTEX_DB_IMAGE:-}" +VORTEX_EXPORT_DB_IMAGE="${VORTEX_EXPORT_DB_IMAGE:-${VORTEX_DB_IMAGE:-}}" # ------------------------------------------------------------------------------ @@ -36,17 +36,17 @@ for cmd in docker; do command -v "${cmd}" >/dev/null || { info "Started database export." -if [ -z "${VORTEX_DB_IMAGE}" ]; then +if [ -z "${VORTEX_EXPORT_DB_IMAGE}" ]; then # Export database as a file. docker compose exec -T cli ./scripts/vortex/export-db-file.sh "$@" else # Export database as a container image. - VORTEX_DB_EXPORT_IMAGE="${VORTEX_DB_IMAGE}" ./scripts/vortex/export-db-image.sh "$@" + VORTEX_EXPORT_DB_IMAGE="${VORTEX_EXPORT_DB_IMAGE}" ./scripts/vortex/export-db-image.sh "$@" # Deploy container image. # @todo Move deployment into a separate script. if [ "${VORTEX_EXPORT_DB_CONTAINER_REGISTRY_DEPLOY_PROCEED:-}" = "1" ]; then - VORTEX_DEPLOY_CONTAINER_REGISTRY_MAP=database=${VORTEX_DB_IMAGE} \ + VORTEX_DEPLOY_CONTAINER_REGISTRY_MAP=database=${VORTEX_EXPORT_DB_IMAGE} \ ./scripts/vortex/deploy-container-registry.sh fi fi diff --git a/scripts/vortex/login-container-registry.sh b/scripts/vortex/login-container-registry.sh index 4e2621b9..f320c066 100755 --- a/scripts/vortex/login-container-registry.sh +++ b/scripts/vortex/login-container-registry.sh @@ -19,20 +19,20 @@ set -eu # Container registry name. # # Provide port, if required as `:`. -VORTEX_CONTAINER_REGISTRY="${VORTEX_CONTAINER_REGISTRY:-docker.io}" +VORTEX_LOGIN_CONTAINER_REGISTRY="${VORTEX_LOGIN_CONTAINER_REGISTRY:-${VORTEX_CONTAINER_REGISTRY:-docker.io}}" # The username to login into the container registry. # # If not provided, the script will skip the login step. -VORTEX_CONTAINER_REGISTRY_USER="${VORTEX_CONTAINER_REGISTRY_USER:-}" +VORTEX_LOGIN_CONTAINER_REGISTRY_USER="${VORTEX_LOGIN_CONTAINER_REGISTRY_USER:-${VORTEX_CONTAINER_REGISTRY_USER:-}}" # The password to login into the container registry. # # If not provided, the script will skip the login step. -VORTEX_CONTAINER_REGISTRY_PASS="${VORTEX_CONTAINER_REGISTRY_PASS:-}" +VORTEX_LOGIN_CONTAINER_REGISTRY_PASS="${VORTEX_LOGIN_CONTAINER_REGISTRY_PASS:-${VORTEX_CONTAINER_REGISTRY_PASS:-}}" # Path to Docker configuration directory. -DOCKER_CONFIG="${DOCKER_CONFIG:-${HOME}/.docker}" +VORTEX_LOGIN_CONTAINER_REGISTRY_DOCKER_CONFIG="${VORTEX_LOGIN_CONTAINER_REGISTRY_DOCKER_CONFIG:-${DOCKER_CONFIG:-${HOME}/.docker}}" # ------------------------------------------------------------------------------ @@ -50,13 +50,13 @@ for cmd in docker; do command -v "${cmd}" >/dev/null || { exit 1 }; done -[ -z "$(echo "${VORTEX_CONTAINER_REGISTRY}" | xargs)" ] && fail "VORTEX_CONTAINER_REGISTRY should not be empty." && exit 1 +[ -z "$(echo "${VORTEX_LOGIN_CONTAINER_REGISTRY}" | xargs)" ] && fail "VORTEX_LOGIN_CONTAINER_REGISTRY or VORTEX_CONTAINER_REGISTRY should not be empty." && exit 1 -if [ -f "${DOCKER_CONFIG}/config.json" ] && grep -q "${VORTEX_CONTAINER_REGISTRY}" "${DOCKER_CONFIG}/config.json"; then - note "Already logged in to the registry \"${VORTEX_CONTAINER_REGISTRY}\"." -elif [ -n "${VORTEX_CONTAINER_REGISTRY_USER}" ] && [ -n "${VORTEX_CONTAINER_REGISTRY_PASS}" ]; then - task "Logging in to registry \"${VORTEX_CONTAINER_REGISTRY}\"." - echo "${VORTEX_CONTAINER_REGISTRY_PASS}" | docker login --username "${VORTEX_CONTAINER_REGISTRY_USER}" --password-stdin "${VORTEX_CONTAINER_REGISTRY}" +if [ -f "${VORTEX_LOGIN_CONTAINER_REGISTRY_DOCKER_CONFIG}/config.json" ] && grep -q "${VORTEX_LOGIN_CONTAINER_REGISTRY}" "${VORTEX_LOGIN_CONTAINER_REGISTRY_DOCKER_CONFIG}/config.json"; then + note "Already logged in to the registry \"${VORTEX_LOGIN_CONTAINER_REGISTRY}\"." +elif [ -n "${VORTEX_LOGIN_CONTAINER_REGISTRY_USER}" ] && [ -n "${VORTEX_LOGIN_CONTAINER_REGISTRY_PASS}" ]; then + task "Logging in to registry \"${VORTEX_LOGIN_CONTAINER_REGISTRY}\"." + echo "${VORTEX_LOGIN_CONTAINER_REGISTRY_PASS}" | docker login --username "${VORTEX_LOGIN_CONTAINER_REGISTRY_USER}" --password-stdin "${VORTEX_LOGIN_CONTAINER_REGISTRY}" else - note "Skipping login to the container registry as either VORTEX_CONTAINER_REGISTRY_USER or VORTEX_CONTAINER_REGISTRY_PASS was not provided." + note "Skipping login to the container registry as either VORTEX_LOGIN_CONTAINER_REGISTRY_USER or VORTEX_CONTAINER_REGISTRY_USER or VORTEX_LOGIN_CONTAINER_REGISTRY_PASS or VORTEX_CONTAINER_REGISTRY_PASS was not provided." fi diff --git a/scripts/vortex/login.sh b/scripts/vortex/login.sh index 791087e6..4332d605 100755 --- a/scripts/vortex/login.sh +++ b/scripts/vortex/login.sh @@ -10,7 +10,7 @@ set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # Flag to unblock admin. -VORTEX_UNBLOCK_ADMIN="${VORTEX_UNBLOCK_ADMIN:-1}" +VORTEX_LOGIN_UNBLOCK_ADMIN="${VORTEX_LOGIN_UNBLOCK_ADMIN:-${VORTEX_UNBLOCK_ADMIN:-1}}" # ------------------------------------------------------------------------------ @@ -24,7 +24,7 @@ fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\03 drush() { ./vendor/bin/drush -y "$@"; } -if [ "${VORTEX_UNBLOCK_ADMIN:-}" = "1" ]; then +if [ "${VORTEX_LOGIN_UNBLOCK_ADMIN:-}" = "1" ]; then if drush pm:list --status=enabled | grep -q password_policy; then drush sql:query 'UPDATE `user__field_password_expiration` SET `field_password_expiration_value` = 0 WHERE `bundle` = "user" AND `entity_id` = 1;' >/dev/null fi diff --git a/scripts/vortex/logout.sh b/scripts/vortex/logout.sh index c1ed2067..fb7eb6ec 100755 --- a/scripts/vortex/logout.sh +++ b/scripts/vortex/logout.sh @@ -10,7 +10,7 @@ set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # Flag to block or unblock admin. -VORTEX_UNBLOCK_ADMIN="${VORTEX_UNBLOCK_ADMIN:-1}" +VORTEX_LOGOUT_BLOCK_ADMIN="${VORTEX_LOGOUT_BLOCK_ADMIN:-${VORTEX_UNBLOCK_ADMIN:-1}}" # ------------------------------------------------------------------------------ @@ -24,6 +24,6 @@ fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\03 drush() { ./vendor/bin/drush -y "$@"; } -if [ "${VORTEX_UNBLOCK_ADMIN:-}" = "1" ]; then +if [ "${VORTEX_LOGOUT_BLOCK_ADMIN:-}" = "1" ]; then drush sql:query "SELECT name FROM \`users_field_data\` WHERE \`uid\` = '1';" | head -n 1 | xargs drush -- user:block fi diff --git a/scripts/vortex/notify-github.sh b/scripts/vortex/notify-github.sh index b179d6db..1c20192f 100755 --- a/scripts/vortex/notify-github.sh +++ b/scripts/vortex/notify-github.sh @@ -32,9 +32,12 @@ VORTEX_NOTIFY_GITHUB_EVENT="${VORTEX_NOTIFY_GITHUB_EVENT:-${VORTEX_NOTIFY_EVENT: # GitHub notification deployment environment URL. VORTEX_NOTIFY_GITHUB_ENVIRONMENT_URL="${VORTEX_NOTIFY_GITHUB_ENVIRONMENT_URL:-${VORTEX_NOTIFY_ENVIRONMENT_URL:-}}" -# GitHub notification environment type: production, uat, dev, pr. +# GitHub notification environment type. # Used as the 'environment' parameter in GitHub's Deployment API. -VORTEX_NOTIFY_GITHUB_ENVIRONMENT_TYPE="${VORTEX_NOTIFY_GITHUB_ENVIRONMENT_TYPE:-PR}" +# Defaults to VORTEX_NOTIFY_LABEL (e.g. "PR-123" or branch name) for unique +# per-PR/branch environments. This prevents cross-PR deployment interference +# where deploying one PR would mark another PR's deployment as inactive. +VORTEX_NOTIFY_GITHUB_ENVIRONMENT_TYPE="${VORTEX_NOTIFY_GITHUB_ENVIRONMENT_TYPE:-${VORTEX_NOTIFY_LABEL:-PR}}" # ------------------------------------------------------------------------------ diff --git a/scripts/vortex/notify-jira.sh b/scripts/vortex/notify-jira.sh index d9ab4065..6baa80a6 100755 --- a/scripts/vortex/notify-jira.sh +++ b/scripts/vortex/notify-jira.sh @@ -24,7 +24,7 @@ VORTEX_NOTIFY_JIRA_USER_EMAIL="${VORTEX_NOTIFY_JIRA_USER_EMAIL:-}" # JIRA notification API token. # -# @see https://www.vortextemplate.com/docs/workflows/notifications#jira +# @see https://www.vortextemplate.com/docs/deployment/notifications#jira VORTEX_NOTIFY_JIRA_TOKEN="${VORTEX_NOTIFY_JIRA_TOKEN:-}" # JIRA notification git branch name (used for issue extraction). diff --git a/scripts/vortex/notify-newrelic.sh b/scripts/vortex/notify-newrelic.sh index 76c29e76..ed7682b9 100755 --- a/scripts/vortex/notify-newrelic.sh +++ b/scripts/vortex/notify-newrelic.sh @@ -26,7 +26,7 @@ VORTEX_NOTIFY_NEWRELIC_PROJECT="${VORTEX_NOTIFY_NEWRELIC_PROJECT:-${VORTEX_NOTIF # 5. The key format is: NRAK-XXXXXXXXXXXXXXXXXXXXXX # # @see https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/#user-key -# @see https://www.vortextemplate.com/docs/workflows/notifications#new-relic +# @see https://www.vortextemplate.com/docs/deployment/notifications#new-relic VORTEX_NOTIFY_NEWRELIC_USER_KEY="${VORTEX_NOTIFY_NEWRELIC_USER_KEY:-${NEWRELIC_USER_KEY:-}}" # New Relic notification deployment label (human-readable identifier for display). diff --git a/scripts/vortex/notify-slack.sh b/scripts/vortex/notify-slack.sh index 12d2cdeb..1b0d72e7 100755 --- a/scripts/vortex/notify-slack.sh +++ b/scripts/vortex/notify-slack.sh @@ -32,7 +32,7 @@ VORTEX_NOTIFY_SLACK_EVENT="${VORTEX_NOTIFY_SLACK_EVENT:-${VORTEX_NOTIFY_EVENT:-p # Slack notification webhook URL. # The incoming Webhook URL from your Slack app configuration. -# @see https://www.vortextemplate.com/docs/workflows/notifications#slack +# @see https://www.vortextemplate.com/docs/deployment/notifications#slack VORTEX_NOTIFY_SLACK_WEBHOOK="${VORTEX_NOTIFY_SLACK_WEBHOOK:-}" # Slack notification target channel (optional, overrides webhook default). diff --git a/scripts/vortex/provision.sh b/scripts/vortex/provision.sh index d31f2b97..42d1867a 100755 --- a/scripts/vortex/provision.sh +++ b/scripts/vortex/provision.sh @@ -19,6 +19,9 @@ VORTEX_PROVISION_SKIP="${VORTEX_PROVISION_SKIP:-}" # Provision type: database or profile. VORTEX_PROVISION_TYPE="${VORTEX_PROVISION_TYPE:-database}" +# Fallback to profile installation if the database dump is not available. +VORTEX_PROVISION_FALLBACK_TO_PROFILE="${VORTEX_PROVISION_FALLBACK_TO_PROFILE:-0}" + # Flag to always overwrite existing database. Usually set to 0 in deployed # environments. VORTEX_PROVISION_OVERRIDE_DB="${VORTEX_PROVISION_OVERRIDE_DB:-0}" @@ -35,6 +38,12 @@ VORTEX_PROVISION_USE_MAINTENANCE_MODE="${VORTEX_PROVISION_USE_MAINTENANCE_MODE:- # state before any updates ran (for example, DB caching in CI). VORTEX_PROVISION_POST_OPERATIONS_SKIP="${VORTEX_PROVISION_POST_OPERATIONS_SKIP:-0}" +# Verify that configuration was not changed by database updates. +# If enabled and config files are present, the provision will fail if +# database update hooks modify active configuration, preventing +# drush config:import from silently overwriting those changes. +VORTEX_PROVISION_VERIFY_CONFIG_UNCHANGED_AFTER_UPDATE="${VORTEX_PROVISION_VERIFY_CONFIG_UNCHANGED_AFTER_UPDATE:-0}" + # Provision database dump file. # If not set, it will be auto-discovered from the VORTEX_DB_DIR directory using # the VORTEX_DB_FILE name. @@ -56,13 +65,13 @@ DRUPAL_SITE_EMAIL="${DRUPAL_SITE_EMAIL:-webmaster@example.com}" DRUPAL_PROFILE="${DRUPAL_PROFILE:-standard}" # Directory with database dump file. -VORTEX_DB_DIR="${VORTEX_DB_DIR:-./.data}" +VORTEX_PROVISION_DB_DIR="${VORTEX_PROVISION_DB_DIR:-${VORTEX_DB_DIR:-./.data}}" # Database dump file name. -VORTEX_DB_FILE="${VORTEX_DB_FILE:-db.sql}" +VORTEX_PROVISION_DB_FILE="${VORTEX_PROVISION_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}" # Name of the pre-built database container image. -VORTEX_DB_IMAGE="${VORTEX_DB_IMAGE:-}" +VORTEX_PROVISION_DB_IMAGE="${VORTEX_PROVISION_DB_IMAGE:-${VORTEX_DB_IMAGE:-}}" # ------------------------------------------------------------------------------ @@ -90,10 +99,10 @@ if [ "${VORTEX_PROVISION_SKIP}" = "1" ]; then fi # Convert DB dir starting with './' to a full path. -[ "${VORTEX_DB_DIR#./}" != "${VORTEX_DB_DIR}" ] && VORTEX_DB_DIR="$(pwd)${VORTEX_DB_DIR#.}" +[ "${VORTEX_PROVISION_DB_DIR#./}" != "${VORTEX_PROVISION_DB_DIR}" ] && VORTEX_PROVISION_DB_DIR="$(pwd)${VORTEX_PROVISION_DB_DIR#.}" if [ -z "${VORTEX_PROVISION_DB}" ]; then - VORTEX_PROVISION_DB="${VORTEX_PROVISION_DB:-"${VORTEX_DB_DIR}/${VORTEX_DB_FILE}"}" + VORTEX_PROVISION_DB="${VORTEX_PROVISION_DB:-"${VORTEX_PROVISION_DB_DIR}/${VORTEX_PROVISION_DB_FILE}"}" fi drush_version="$(drush --version | cut -d' ' -f4)" @@ -122,8 +131,8 @@ note "Private files path : ${DRUPAL_PRIVATE_FILES-}" note "Temporary files path : ${DRUPAL_TEMPORARY_FILES-}" note "Config files path : ${config_path}" note "DB dump file path : ${VORTEX_PROVISION_DB} ($([ -f "${VORTEX_PROVISION_DB}" ] && echo "present" || echo "absent"))" -if [ -n "${VORTEX_DB_IMAGE:-}" ]; then - note "DB dump container image : ${VORTEX_DB_IMAGE-}" +if [ -n "${VORTEX_PROVISION_DB_IMAGE:-}" ]; then + note "DB dump container image : ${VORTEX_PROVISION_DB_IMAGE-}" fi echo note "Profile : ${DRUPAL_PROFILE}" @@ -131,22 +140,38 @@ note "Configuration files present : $(yesno "${site_has_config_files}")" note "Existing site found : $(yesno "${site_is_installed}")" echo note "Provision type : ${VORTEX_PROVISION_TYPE}" +note "Fallback to profile : $(yesno "${VORTEX_PROVISION_FALLBACK_TO_PROFILE}")" note "Overwrite existing DB : $(yesno "${VORTEX_PROVISION_OVERRIDE_DB}")" note "Skip DB sanitization : $(yesno "${VORTEX_PROVISION_SANITIZE_DB_SKIP}")" note "Skip post-provision operations : $(yesno "${VORTEX_PROVISION_POST_OPERATIONS_SKIP}")" +note "Verify config after update : $(yesno "${VORTEX_PROVISION_VERIFY_CONFIG_UNCHANGED_AFTER_UPDATE}")" note "Use maintenance mode : $(yesno "${VORTEX_PROVISION_USE_MAINTENANCE_MODE}")" echo ################################################################################ +if [ "${VORTEX_PROVISION_VERIFY_CONFIG_UNCHANGED_AFTER_UPDATE}" = "1" ]; then + for cmd in diff mktemp; do command -v "${cmd}" >/dev/null || { + fail "Command ${cmd} is not available" + exit 1 + }; done +fi + # # Provision site by importing the database from the dump file. # provision_from_db() { if [ ! -f "${VORTEX_PROVISION_DB}" ]; then + if [ "${VORTEX_PROVISION_FALLBACK_TO_PROFILE}" = "1" ]; then + info "Database dump file is not available. Falling back to profile installation." + provision_from_profile + return + fi + echo fail "Unable to import database from file." note "Dump file ${VORTEX_PROVISION_DB} does not exist." note "Site content was not changed." + exit 1 fi @@ -196,7 +221,7 @@ if [ "${VORTEX_PROVISION_TYPE}" = "database" ]; then if [ "${site_is_installed}" = "1" ]; then note "Existing site was found." - if [ -n "${VORTEX_DB_IMAGE-}" ]; then + if [ -n "${VORTEX_PROVISION_DB_IMAGE-}" ]; then note "Database is baked into the container image." note "Site content will be preserved." # Container image restarts with a fresh database. Let the downstream @@ -213,17 +238,23 @@ if [ "${VORTEX_PROVISION_TYPE}" = "database" ]; then else note "Existing site was not found." - if [ -n "${VORTEX_DB_IMAGE-}" ]; then + if [ -n "${VORTEX_PROVISION_DB_IMAGE-}" ]; then note "Database is baked into the container image." - note "Looks like the database in the container image is corrupted." - note "Site content was not changed." - exit 1 + if [ "${VORTEX_PROVISION_FALLBACK_TO_PROFILE}" = "1" ]; then + info "Database in the container image is not available. Falling back to profile installation." + provision_from_profile + export VORTEX_PROVISION_OVERRIDE_DB=1 + else + note "Looks like the database in the container image is corrupted." + note "Site content was not changed." + exit 1 + fi + else + note "Fresh site content will be imported from the database dump file." + provision_from_db + # Let the downstream scripts know that the database is fresh. + export VORTEX_PROVISION_OVERRIDE_DB=1 fi - - note "Fresh site content will be imported from the database dump file." - provision_from_db - # Let the downstream scripts know that the database is fresh. - export VORTEX_PROVISION_OVERRIDE_DB=1 fi else info "Provisioning site from the profile." @@ -271,7 +302,7 @@ if [ "${VORTEX_PROVISION_USE_MAINTENANCE_MODE}" = "1" ]; then echo fi -# Use 'drush deploy' if configuration files are present or use standalone commands otherwise. +# Set site UUID from configuration if config files are present. if [ "${site_has_config_files}" = "1" ]; then if [ -f "${config_path}/system.site.yml" ]; then config_uuid="$(cat "${config_path}/system.site.yml" | grep uuid | tail -c +7 | head -c 36)" @@ -279,10 +310,46 @@ if [ "${site_has_config_files}" = "1" ]; then pass "Updated site UUID from the configuration with ${config_uuid}." echo fi +fi + +task "Running database updates." + +if [ "${VORTEX_PROVISION_VERIFY_CONFIG_UNCHANGED_AFTER_UPDATE}" = "1" ] && [ "${site_has_config_files}" = "1" ]; then + config_before=$(mktemp -d) + drush config:export --destination="${config_before}" + + drush updatedb --no-cache-clear + + config_after=$(mktemp -d) + drush config:export --destination="${config_after}" + + config_diff=$(diff -rq "${config_before}" "${config_after}" || true) + + if [ -n "${config_diff}" ]; then + fail "Configuration was changed by database updates." + note "The following configuration files were changed:" + echo "${config_diff}" + note "Configuration before updates: ${config_before}" + note "Configuration after updates: ${config_after}" + note "Review the update hooks and manually export updated configuration." + exit 1 + fi + + rm -rf "${config_before}" "${config_after}" + + pass "Verified that database updates did not change configuration." +else + drush updatedb --no-cache-clear +fi + +pass "Completed running database updates." +echo - task "Running deployment operations via 'drush deploy'." - drush deploy - pass "Completed deployment operations via 'drush deploy'." +# Import configuration if config files are present. +if [ "${site_has_config_files}" = "1" ]; then + task "Importing configuration." + drush config:import + pass "Completed configuration import." echo # Import config_split configuration if the module is installed. @@ -295,22 +362,17 @@ if [ "${site_has_config_files}" = "1" ]; then pass "Completed config_split configuration import." echo fi -else - task "Running database updates." - drush updatedb --no-cache-clear - pass "Completed running database updates." - echo +fi - task "Rebuilding cache." - drush cache:rebuild - pass "Cache was rebuilt." - echo +task "Rebuilding cache." +drush cache:rebuild +pass "Cache was rebuilt." +echo - task "Running deployment operations via 'drush deploy:hook'." - drush deploy:hook - pass "Completed deployment operations via 'drush deploy:hook'." - echo -fi +task "Running deployment hooks." +drush deploy:hook +pass "Completed deployment hooks." +echo # Sanitize database. if [ "${VORTEX_PROVISION_SANITIZE_DB_SKIP}" != "1" ]; then diff --git a/scripts/vortex/task-copy-db-acquia.sh b/scripts/vortex/task-copy-db-acquia.sh index e12d31cf..c5f4add7 100755 --- a/scripts/vortex/task-copy-db-acquia.sh +++ b/scripts/vortex/task-copy-db-acquia.sh @@ -19,13 +19,13 @@ set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # Acquia Cloud API key. -VORTEX_ACQUIA_KEY="${VORTEX_ACQUIA_KEY:-${VORTEX_ACQUIA_KEY}}" +VORTEX_TASK_COPY_DB_ACQUIA_KEY="${VORTEX_TASK_COPY_DB_ACQUIA_KEY:-${VORTEX_ACQUIA_KEY:-}}" # Acquia Cloud API secret. -VORTEX_ACQUIA_SECRET="${VORTEX_ACQUIA_SECRET:-${VORTEX_ACQUIA_SECRET}}" +VORTEX_TASK_COPY_DB_ACQUIA_SECRET="${VORTEX_TASK_COPY_DB_ACQUIA_SECRET:-${VORTEX_ACQUIA_SECRET:-}}" # Application name. Used to discover UUID. -VORTEX_ACQUIA_APP_NAME="${VORTEX_ACQUIA_APP_NAME:-${VORTEX_ACQUIA_APP_NAME}}" +VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME="${VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME:-${VORTEX_ACQUIA_APP_NAME:-}}" # Source environment name to copy DB from. VORTEX_TASK_COPY_DB_ACQUIA_SRC="${VORTEX_TASK_COPY_DB_ACQUIA_SRC:-}" @@ -79,9 +79,9 @@ for cmd in curl; do command -v "${cmd}" >/dev/null || { }; done # Check that all required variables are present. -[ -z "${VORTEX_ACQUIA_KEY}" ] && fail "Missing value for VORTEX_ACQUIA_KEY." && exit 1 -[ -z "${VORTEX_ACQUIA_SECRET}" ] && fail "Missing value for VORTEX_ACQUIA_SECRET." && exit 1 -[ -z "${VORTEX_ACQUIA_APP_NAME}" ] && fail "Missing value for VORTEX_ACQUIA_APP_NAME." && exit 1 +[ -z "${VORTEX_TASK_COPY_DB_ACQUIA_KEY}" ] && fail "Missing value for VORTEX_TASK_COPY_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY." && exit 1 +[ -z "${VORTEX_TASK_COPY_DB_ACQUIA_SECRET}" ] && fail "Missing value for VORTEX_TASK_COPY_DB_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." && exit 1 +[ -z "${VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME}" ] && fail "Missing value for VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME or VORTEX_ACQUIA_APP_NAME." && exit 1 [ -z "${VORTEX_TASK_COPY_DB_ACQUIA_SRC}" ] && fail "Missing value for VORTEX_TASK_COPY_DB_ACQUIA_SRC." && exit 1 [ -z "${VORTEX_TASK_COPY_DB_ACQUIA_DST}" ] && fail "Missing value for VORTEX_TASK_COPY_DB_ACQUIA_DST." && exit 1 [ -z "${VORTEX_TASK_COPY_DB_ACQUIA_NAME}" ] && fail "Missing value for VORTEX_TASK_COPY_DB_ACQUIA_NAME." && exit 1 @@ -89,12 +89,12 @@ for cmd in curl; do command -v "${cmd}" >/dev/null || { [ -z "${VORTEX_TASK_COPY_DB_ACQUIA_STATUS_INTERVAL}" ] && fail "Missing value for VORTEX_TASK_COPY_DB_ACQUIA_STATUS_INTERVAL." && exit 1 task "Retrieving authentication token." -token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") +token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_COPY_DB_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_COPY_DB_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") token=$(echo "${token_json}" | extract_json_value "access_token") [ -z "${token}" ] && fail "Unable to retrieve a token." && exit 1 -task "Retrieving ${VORTEX_ACQUIA_APP_NAME} application UUID." -app_uuid_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications?filter=name%3D${VORTEX_ACQUIA_APP_NAME/ /%20}") +task "Retrieving ${VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME} application UUID." +app_uuid_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications?filter=name%3D${VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME/ /%20}") app_uuid=$(echo "${app_uuid_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "uuid") [ -z "${app_uuid}" ] && fail "Unable to retrieve an environment UUID." && exit 1 @@ -123,7 +123,7 @@ for i in $(seq 1 "${VORTEX_TASK_COPY_DB_ACQUIA_STATUS_RETRIES}"); do [ "${task_state}" = "completed" ] && task_completed=1 && break task "Retrieving authentication token." - token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") + token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_COPY_DB_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_COPY_DB_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") token=$(echo "${token_json}" | extract_json_value "access_token") [ -z "${token}" ] && fail "Unable to retrieve a token." && exit 1 done diff --git a/scripts/vortex/task-copy-files-acquia.sh b/scripts/vortex/task-copy-files-acquia.sh index d2797e0f..6b6306ba 100755 --- a/scripts/vortex/task-copy-files-acquia.sh +++ b/scripts/vortex/task-copy-files-acquia.sh @@ -19,13 +19,13 @@ set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # Acquia Cloud API key. -VORTEX_ACQUIA_KEY="${VORTEX_ACQUIA_KEY:-${VORTEX_ACQUIA_KEY}}" +VORTEX_TASK_COPY_FILES_ACQUIA_KEY="${VORTEX_TASK_COPY_FILES_ACQUIA_KEY:-${VORTEX_ACQUIA_KEY:-}}" # Acquia Cloud API secret. -VORTEX_ACQUIA_SECRET="${VORTEX_ACQUIA_SECRET:-${VORTEX_ACQUIA_SECRET}}" +VORTEX_TASK_COPY_FILES_ACQUIA_SECRET="${VORTEX_TASK_COPY_FILES_ACQUIA_SECRET:-${VORTEX_ACQUIA_SECRET:-}}" # Application name. Used to discover UUID. -VORTEX_ACQUIA_APP_NAME="${VORTEX_ACQUIA_APP_NAME:-${VORTEX_ACQUIA_APP_NAME}}" +VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME="${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME:-${VORTEX_ACQUIA_APP_NAME:-}}" # Source environment name to copy from. VORTEX_TASK_COPY_FILES_ACQUIA_SRC="${VORTEX_TASK_COPY_FILES_ACQUIA_SRC:-}" @@ -75,21 +75,21 @@ extract_json_value() { } # Check that all required variables are present. -[ -z "${VORTEX_ACQUIA_KEY}" ] && fail "Missing value for VORTEX_ACQUIA_KEY." && exit 1 -[ -z "${VORTEX_ACQUIA_SECRET}" ] && fail "Missing value for VORTEX_ACQUIA_SECRET." && exit 1 -[ -z "${VORTEX_ACQUIA_APP_NAME}" ] && fail "Missing value for VORTEX_ACQUIA_APP_NAME." && exit 1 +[ -z "${VORTEX_TASK_COPY_FILES_ACQUIA_KEY}" ] && fail "Missing value for VORTEX_TASK_COPY_FILES_ACQUIA_KEY or VORTEX_ACQUIA_KEY." && exit 1 +[ -z "${VORTEX_TASK_COPY_FILES_ACQUIA_SECRET}" ] && fail "Missing value for VORTEX_TASK_COPY_FILES_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." && exit 1 +[ -z "${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME}" ] && fail "Missing value for VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME or VORTEX_ACQUIA_APP_NAME." && exit 1 [ -z "${VORTEX_TASK_COPY_FILES_ACQUIA_SRC}" ] && fail "Missing value for VORTEX_TASK_COPY_FILES_ACQUIA_SRC." && exit 1 [ -z "${VORTEX_TASK_COPY_FILES_ACQUIA_DST}" ] && fail "Missing value for VORTEX_TASK_COPY_FILES_ACQUIA_DST." && exit 1 [ -z "${VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_RETRIES}" ] && fail "Missing value for VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_RETRIES." && exit 1 [ -z "${VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_INTERVAL}" ] && fail "Missing value for VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_INTERVAL." && exit 1 task "Retrieving authentication token." -token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") +token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_COPY_FILES_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_COPY_FILES_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") token=$(echo "${token_json}" | extract_json_value "access_token") [ -z "${token}" ] && fail "Unable to retrieve a token." && exit 1 -task "Retrieving ${VORTEX_ACQUIA_APP_NAME} application UUID." -app_uuid_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications?filter=name%3D${VORTEX_ACQUIA_APP_NAME/ /%20}") +task "Retrieving ${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME} application UUID." +app_uuid_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications?filter=name%3D${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME/ /%20}") app_uuid=$(echo "${app_uuid_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "uuid") [ -z "${app_uuid}" ] && fail "Unable to retrieve an environment UUID." && exit 1 @@ -118,7 +118,7 @@ for i in $(seq 1 "${VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_RETRIES}"); do [ "${task_state}" = "completed" ] && task_completed=1 && break task "Retrieving authentication token." - token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") + token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_COPY_FILES_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_COPY_FILES_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") token=$(echo "${token_json}" | extract_json_value "access_token") [ -z "${token}" ] && fail "Unable to retrieve a token." && exit 1 done diff --git a/scripts/vortex/task-custom-lagoon.sh b/scripts/vortex/task-custom-lagoon.sh index 9a123d5d..562a9c40 100755 --- a/scripts/vortex/task-custom-lagoon.sh +++ b/scripts/vortex/task-custom-lagoon.sh @@ -12,43 +12,43 @@ set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # The task name. -VORTEX_TASK_LAGOON_NAME="${VORTEX_TASK_LAGOON_NAME:-Automation task}" +VORTEX_TASK_CUSTOM_LAGOON_NAME="${VORTEX_TASK_CUSTOM_LAGOON_NAME:-Automation task}" # The Lagoon project to run tasks for. -VORTEX_TASK_LAGOON_PROJECT="${VORTEX_TASK_LAGOON_PROJECT:-${LAGOON_PROJECT:-}}" +VORTEX_TASK_CUSTOM_LAGOON_PROJECT="${VORTEX_TASK_CUSTOM_LAGOON_PROJECT:-${LAGOON_PROJECT:-}}" # The Lagoon branch to run the task on. -VORTEX_TASK_LAGOON_BRANCH="${VORTEX_TASK_LAGOON_BRANCH:-}" +VORTEX_TASK_CUSTOM_LAGOON_BRANCH="${VORTEX_TASK_CUSTOM_LAGOON_BRANCH:-}" # The task command to execute. -VORTEX_TASK_LAGOON_COMMAND="${VORTEX_TASK_LAGOON_COMMAND:-}" +VORTEX_TASK_CUSTOM_LAGOON_COMMAND="${VORTEX_TASK_CUSTOM_LAGOON_COMMAND:-}" # The Lagoon instance name to interact with. -VORTEX_TASK_LAGOON_INSTANCE="${VORTEX_TASK_LAGOON_INSTANCE:-amazeeio}" +VORTEX_TASK_CUSTOM_LAGOON_INSTANCE="${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE:-amazeeio}" # The Lagoon instance GraphQL endpoint to interact with. -VORTEX_TASK_LAGOON_INSTANCE_GRAPHQL="${VORTEX_TASK_LAGOON_INSTANCE_GRAPHQL:-https://api.lagoon.amazeeio.cloud/graphql}" +VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_GRAPHQL="${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_GRAPHQL:-https://api.lagoon.amazeeio.cloud/graphql}" # The Lagoon instance hostname to interact with. -VORTEX_TASK_LAGOON_INSTANCE_HOSTNAME="${VORTEX_TASK_LAGOON_INSTANCE_HOSTNAME:-ssh.lagoon.amazeeio.cloud}" +VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_HOSTNAME="${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_HOSTNAME:-ssh.lagoon.amazeeio.cloud}" # The Lagoon instance port to interact with. -VORTEX_TASK_LAGOON_INSTANCE_PORT="${VORTEX_TASK_LAGOON_INSTANCE_PORT:-32222}" +VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_PORT="${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_PORT:-32222}" # SSH key fingerprint used to connect to a remote. -VORTEX_TASK_SSH_FINGERPRINT="${VORTEX_TASK_SSH_FINGERPRINT:-}" +VORTEX_TASK_CUSTOM_LAGOON_SSH_FINGERPRINT="${VORTEX_TASK_CUSTOM_LAGOON_SSH_FINGERPRINT:-}" # Default SSH file used if custom fingerprint is not provided. -VORTEX_TASK_SSH_FILE="${VORTEX_TASK_SSH_FILE:-${HOME}/.ssh/id_rsa}" +VORTEX_TASK_CUSTOM_LAGOON_SSH_FILE="${VORTEX_TASK_CUSTOM_LAGOON_SSH_FILE:-${HOME}/.ssh/id_rsa}" # Location of the Lagoon CLI binary. -VORTEX_LAGOONCLI_PATH="${VORTEX_LAGOONCLI_PATH:-/tmp}" +VORTEX_TASK_CUSTOM_LAGOON_CLI_PATH="${VORTEX_TASK_CUSTOM_LAGOON_CLI_PATH:-/tmp}" # Flag to force the installation of Lagoon CLI. -VORTEX_LAGOONCLI_FORCE_INSTALL="${VORTEX_LAGOONCLI_FORCE_INSTALL:-}" +VORTEX_TASK_CUSTOM_LAGOON_CLI_FORCE_INSTALL="${VORTEX_TASK_CUSTOM_LAGOON_CLI_FORCE_INSTALL:-}" # Lagoon CLI version to use. -VORTEX_LAGOONCLI_VERSION="${VORTEX_LAGOONCLI_VERSION:-v0.32.0}" +VORTEX_TASK_CUSTOM_LAGOON_CLI_VERSION="${VORTEX_TASK_CUSTOM_LAGOON_CLI_VERSION:-v0.32.0}" # ------------------------------------------------------------------------------ @@ -60,29 +60,29 @@ pass() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\03 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 Lagoon task ${VORTEX_TASK_LAGOON_NAME}." +info "Started Lagoon task ${VORTEX_TASK_CUSTOM_LAGOON_NAME}." ## Check all required values. -[ -z "${VORTEX_TASK_LAGOON_NAME}" ] && fail "Missing required value for VORTEX_TASK_LAGOON_NAME." && exit 1 -[ -z "${VORTEX_TASK_LAGOON_BRANCH}" ] && fail "Missing required value for VORTEX_TASK_LAGOON_BRANCH." && exit 1 -[ -z "${VORTEX_TASK_LAGOON_COMMAND}" ] && fail "Missing required value for VORTEX_TASK_LAGOON_COMMAND." && exit 1 -[ -z "${VORTEX_TASK_LAGOON_PROJECT}" ] && fail "Missing required value for VORTEX_TASK_LAGOON_PROJECT." && exit 1 +[ -z "${VORTEX_TASK_CUSTOM_LAGOON_NAME}" ] && fail "Missing required value for VORTEX_TASK_CUSTOM_LAGOON_NAME or VORTEX_TASK_LAGOON_NAME." && exit 1 +[ -z "${VORTEX_TASK_CUSTOM_LAGOON_BRANCH}" ] && fail "Missing required value for VORTEX_TASK_CUSTOM_LAGOON_BRANCH or VORTEX_TASK_LAGOON_BRANCH." && exit 1 +[ -z "${VORTEX_TASK_CUSTOM_LAGOON_COMMAND}" ] && fail "Missing required value for VORTEX_TASK_CUSTOM_LAGOON_COMMAND or VORTEX_TASK_LAGOON_COMMAND." && exit 1 +[ -z "${VORTEX_TASK_CUSTOM_LAGOON_PROJECT}" ] && fail "Missing required value for VORTEX_TASK_CUSTOM_LAGOON_PROJECT or VORTEX_TASK_LAGOON_PROJECT." && exit 1 -export VORTEX_SSH_PREFIX="TASK" && . ./scripts/vortex/setup-ssh.sh +export VORTEX_SSH_PREFIX="TASK_CUSTOM_LAGOON" && . ./scripts/vortex/setup-ssh.sh -if ! command -v lagoon >/dev/null || [ -n "${VORTEX_LAGOONCLI_FORCE_INSTALL}" ]; then +if ! command -v lagoon >/dev/null || [ -n "${VORTEX_TASK_CUSTOM_LAGOON_CLI_FORCE_INSTALL}" ]; then task "Installing Lagoon CLI." platform=$(uname -s | tr '[:upper:]' '[:lower:]') arch_suffix=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') - download_url="https://github.com/uselagoon/lagoon-cli/releases/download/${VORTEX_LAGOONCLI_VERSION}/lagoon-cli-${VORTEX_LAGOONCLI_VERSION}-${platform}-${arch_suffix}" + download_url="https://github.com/uselagoon/lagoon-cli/releases/download/${VORTEX_TASK_CUSTOM_LAGOON_CLI_VERSION}/lagoon-cli-${VORTEX_TASK_CUSTOM_LAGOON_CLI_VERSION}-${platform}-${arch_suffix}" note "Downloading Lagoon CLI from ${download_url}." - curl -fSLs -o "${VORTEX_LAGOONCLI_PATH}/lagoon" "${download_url}" + curl -fSLs -o "${VORTEX_TASK_CUSTOM_LAGOON_CLI_PATH}/lagoon" "${download_url}" - note "Installing Lagoon CLI to ${VORTEX_LAGOONCLI_PATH}/lagoon." - chmod +x "${VORTEX_LAGOONCLI_PATH}/lagoon" - export PATH="${PATH}:${VORTEX_LAGOONCLI_PATH}" + note "Installing Lagoon CLI to ${VORTEX_TASK_CUSTOM_LAGOON_CLI_PATH}/lagoon." + chmod +x "${VORTEX_TASK_CUSTOM_LAGOON_CLI_PATH}/lagoon" + export PATH="${PATH}:${VORTEX_TASK_CUSTOM_LAGOON_CLI_PATH}" fi for cmd in curl lagoon; do command -v "${cmd}" >/dev/null || { @@ -92,11 +92,11 @@ for cmd in curl lagoon; do command -v "${cmd}" >/dev/null || { task "Configuring Lagoon instance." #shellcheck disable=SC2218 -lagoon config add --force -l "${VORTEX_TASK_LAGOON_INSTANCE}" -g "${VORTEX_TASK_LAGOON_INSTANCE_GRAPHQL}" -H "${VORTEX_TASK_LAGOON_INSTANCE_HOSTNAME}" -P "${VORTEX_TASK_LAGOON_INSTANCE_PORT}" +lagoon config add --force -l "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE}" -g "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_GRAPHQL}" -H "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_HOSTNAME}" -P "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_PORT}" -lagoon() { command lagoon --force --skip-update-check -i "${VORTEX_TASK_SSH_FILE}" -l "${VORTEX_TASK_LAGOON_INSTANCE}" -p "${VORTEX_TASK_LAGOON_PROJECT}" "$@"; } +lagoon() { command lagoon --force --skip-update-check -i "${VORTEX_TASK_CUSTOM_LAGOON_SSH_FILE}" -l "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE}" -p "${VORTEX_TASK_CUSTOM_LAGOON_PROJECT}" "$@"; } -task "Creating ${VORTEX_TASK_LAGOON_NAME} task: project ${VORTEX_TASK_LAGOON_PROJECT}, branch: ${VORTEX_TASK_LAGOON_BRANCH}." -lagoon run custom -e "${VORTEX_TASK_LAGOON_BRANCH}" -N "${VORTEX_TASK_LAGOON_NAME}" -c "${VORTEX_TASK_LAGOON_COMMAND}" +task "Creating ${VORTEX_TASK_CUSTOM_LAGOON_NAME} task: project ${VORTEX_TASK_CUSTOM_LAGOON_PROJECT}, branch: ${VORTEX_TASK_CUSTOM_LAGOON_BRANCH}." +lagoon run custom -e "${VORTEX_TASK_CUSTOM_LAGOON_BRANCH}" -N "${VORTEX_TASK_CUSTOM_LAGOON_NAME}" -c "${VORTEX_TASK_CUSTOM_LAGOON_COMMAND}" -pass "Finished Lagoon task ${VORTEX_TASK_LAGOON_NAME}." +pass "Finished Lagoon task ${VORTEX_TASK_CUSTOM_LAGOON_NAME}." diff --git a/scripts/vortex/task-purge-cache-acquia.sh b/scripts/vortex/task-purge-cache-acquia.sh index 23dcbaad..ccf38752 100755 --- a/scripts/vortex/task-purge-cache-acquia.sh +++ b/scripts/vortex/task-purge-cache-acquia.sh @@ -19,13 +19,13 @@ set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x # Acquia Cloud API key. -VORTEX_ACQUIA_KEY="${VORTEX_ACQUIA_KEY:-${VORTEX_ACQUIA_KEY}}" +VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY="${VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY:-${VORTEX_ACQUIA_KEY:-}}" # Acquia Cloud API secret. -VORTEX_ACQUIA_SECRET="${VORTEX_ACQUIA_SECRET:-${VORTEX_ACQUIA_SECRET}}" +VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET="${VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET:-${VORTEX_ACQUIA_SECRET:-}}" # Application name. Used to discover UUID. -VORTEX_ACQUIA_APP_NAME="${VORTEX_ACQUIA_APP_NAME:-}" +VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME="${VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME:-${VORTEX_ACQUIA_APP_NAME:-}}" # An environment name to purge cache for. VORTEX_TASK_PURGE_CACHE_ACQUIA_ENV="${VORTEX_TASK_PURGE_CACHE_ACQUIA_ENV:-}" @@ -75,21 +75,21 @@ extract_json_value() { } # Check that all required variables are present. -[ -z "${VORTEX_ACQUIA_KEY}" ] && fail "Missing value for VORTEX_ACQUIA_KEY." && exit 1 -[ -z "${VORTEX_ACQUIA_SECRET}" ] && fail "Missing value for VORTEX_ACQUIA_SECRET." && exit 1 -[ -z "${VORTEX_ACQUIA_APP_NAME}" ] && fail "Missing value for VORTEX_ACQUIA_APP_NAME." && exit 1 +[ -z "${VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY}" ] && fail "Missing value for VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY or VORTEX_ACQUIA_KEY." && exit 1 +[ -z "${VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET}" ] && fail "Missing value for VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." && exit 1 +[ -z "${VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME}" ] && fail "Missing value for VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME or VORTEX_ACQUIA_APP_NAME." && exit 1 [ -z "${VORTEX_TASK_PURGE_CACHE_ACQUIA_ENV}" ] && fail "Missing value for VORTEX_TASK_PURGE_CACHE_ACQUIA_ENV." && exit 1 [ -z "${VORTEX_TASK_PURGE_CACHE_ACQUIA_DOMAINS_FILE}" ] && fail "Missing value for VORTEX_TASK_PURGE_CACHE_ACQUIA_DOMAINS_FILE." && exit 1 [ -z "${VORTEX_TASK_PURGE_CACHE_ACQUIA_STATUS_RETRIES}" ] && fail "Missing value for VORTEX_TASK_PURGE_CACHE_ACQUIA_STATUS_RETRIES." && exit 1 [ -z "${VORTEX_TASK_PURGE_CACHE_ACQUIA_STATUS_INTERVAL}" ] && fail "Missing value for VORTEX_TASK_PURGE_CACHE_ACQUIA_STATUS_INTERVAL." && exit 1 task "Retrieving authentication token." -token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") +token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") token=$(echo "${token_json}" | extract_json_value "access_token") [ -z "${token}" ] && fail "Unable to retrieve a token." && exit 1 -task "Retrieving ${VORTEX_ACQUIA_APP_NAME} application UUID." -app_uuid_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications?filter=name%3D${VORTEX_ACQUIA_APP_NAME/ /%20}") +task "Retrieving ${VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME} application UUID." +app_uuid_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications?filter=name%3D${VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME/ /%20}") app_uuid=$(echo "${app_uuid_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "uuid") [ -z "${app_uuid}" ] && fail "Unable to retrieve an environment UUID." && exit 1 @@ -161,7 +161,7 @@ if [ "${#domain_list[@]}" -gt 0 ]; then fi task "Retrieving authentication token." - token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") + token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") token=$(echo "${token_json}" | extract_json_value "access_token") [ -z "${token}" ] && fail "Unable to retrieve a token." && exit 1 done diff --git a/scripts/vortex/upload-db-s3.sh b/scripts/vortex/upload-db-s3.sh new file mode 100755 index 00000000..7d4a3f44 --- /dev/null +++ b/scripts/vortex/upload-db-s3.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +## +# Upload DB dump to S3. +# +# Uses AWS Signature Version 4 with curl (no AWS CLI required). +# +# IMPORTANT! This script runs outside the container on the host system. +# +# shellcheck disable=SC1090,SC1091 + +t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t + +set -eu +[ "${VORTEX_DEBUG-}" = "1" ] && set -x + +# AWS access key. +VORTEX_UPLOAD_DB_S3_ACCESS_KEY="${VORTEX_UPLOAD_DB_S3_ACCESS_KEY:-${S3_ACCESS_KEY:-}}" + +# AWS secret key. +VORTEX_UPLOAD_DB_S3_SECRET_KEY="${VORTEX_UPLOAD_DB_S3_SECRET_KEY:-${S3_SECRET_KEY:-}}" + +# S3 bucket name. +VORTEX_UPLOAD_DB_S3_BUCKET="${VORTEX_UPLOAD_DB_S3_BUCKET:-${S3_BUCKET:-}}" + +# S3 region. +VORTEX_UPLOAD_DB_S3_REGION="${VORTEX_UPLOAD_DB_S3_REGION:-${S3_REGION:-}}" + +# S3 prefix (path within the bucket). +VORTEX_UPLOAD_DB_S3_PREFIX="${VORTEX_UPLOAD_DB_S3_PREFIX:-${S3_PREFIX:-}}" + +# Directory with database dump file. +VORTEX_UPLOAD_DB_S3_DB_DIR="${VORTEX_UPLOAD_DB_S3_DB_DIR:-${VORTEX_DB_DIR:-./.data}}" + +# Database dump file name. +VORTEX_UPLOAD_DB_S3_DB_FILE="${VORTEX_UPLOAD_DB_S3_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}" + +# Remote database dump file name. +VORTEX_UPLOAD_DB_S3_REMOTE_FILE="${VORTEX_UPLOAD_DB_S3_REMOTE_FILE:-db.sql}" + +# S3 storage class. +VORTEX_UPLOAD_DB_S3_STORAGE_CLASS="${VORTEX_UPLOAD_DB_S3_STORAGE_CLASS:-STANDARD}" + +# ------------------------------------------------------------------------------ + +# @formatter:off +note() { printf " %s\n" "${1}"; } +task() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[TASK] %s\033[0m\n" "${1}" || printf "[TASK] %s\n" "${1}"; } +info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[36m[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 + +for cmd in curl openssl; do command -v "${cmd}" >/dev/null || { + fail "Command ${cmd} is not available" + exit 1 +}; done + +[ -z "${VORTEX_UPLOAD_DB_S3_ACCESS_KEY}" ] && fail "Missing required value for VORTEX_UPLOAD_DB_S3_ACCESS_KEY." && exit 1 +[ -z "${VORTEX_UPLOAD_DB_S3_SECRET_KEY}" ] && fail "Missing required value for VORTEX_UPLOAD_DB_S3_SECRET_KEY." && exit 1 +[ -z "${VORTEX_UPLOAD_DB_S3_BUCKET}" ] && fail "Missing required value for VORTEX_UPLOAD_DB_S3_BUCKET." && exit 1 +[ -z "${VORTEX_UPLOAD_DB_S3_REGION}" ] && fail "Missing required value for VORTEX_UPLOAD_DB_S3_REGION." && exit 1 + +local_file="${VORTEX_UPLOAD_DB_S3_DB_DIR}/${VORTEX_UPLOAD_DB_S3_DB_FILE}" +[ ! -f "${local_file}" ] && fail "Database dump file ${local_file} does not exist." && exit 1 + +info "Started database dump upload to S3." + +# Ensure prefix ends with a trailing slash if non-empty. +[ -n "${VORTEX_UPLOAD_DB_S3_PREFIX}" ] && VORTEX_UPLOAD_DB_S3_PREFIX="${VORTEX_UPLOAD_DB_S3_PREFIX%/}/" + +request_type="PUT" +auth_type="AWS4-HMAC-SHA256" +service="s3" +base_url=".${service}.${VORTEX_UPLOAD_DB_S3_REGION}.amazonaws.com" +date_short=$(date -u +'%Y%m%d') +date_long=$(date -u +'%Y%m%dT%H%M%SZ') +object_url="https://${VORTEX_UPLOAD_DB_S3_BUCKET}${base_url}/${VORTEX_UPLOAD_DB_S3_PREFIX}${VORTEX_UPLOAD_DB_S3_REMOTE_FILE}" + +note "Local file: ${local_file}" +note "Remote file: ${VORTEX_UPLOAD_DB_S3_PREFIX}${VORTEX_UPLOAD_DB_S3_REMOTE_FILE}" +note "S3 bucket: ${VORTEX_UPLOAD_DB_S3_BUCKET}" +note "S3 region: ${VORTEX_UPLOAD_DB_S3_REGION}" +[ -n "${VORTEX_UPLOAD_DB_S3_PREFIX}" ] && note "S3 prefix: ${VORTEX_UPLOAD_DB_S3_PREFIX}" +note "Storage class: ${VORTEX_UPLOAD_DB_S3_STORAGE_CLASS}" + +if hash file 2>/dev/null; then + content_type="$(file --brief --mime-type "${local_file}")" +else + content_type='application/octet-stream' +fi + +payload_hash=$(openssl dgst -sha256 -hex <"${local_file}" 2>/dev/null | sed 's/^.* //') + +aws_sign4() { + l_date=$(printf '%s' "$2" | openssl dgst -sha256 -hex -mac HMAC -macopt "key:AWS4$1" 2>/dev/null | sed 's/^.* //') + l_region=$(printf '%s' "$3" | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${l_date}" 2>/dev/null | sed 's/^.* //') + l_service=$(printf '%s' "$4" | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${l_region}" 2>/dev/null | sed 's/^.* //') + l_signing=$(printf 'aws4_request' | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${l_service}" 2>/dev/null | sed 's/^.* //') + printf '%s' "$5" | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${l_signing}" 2>/dev/null | sed 's/^.* //' +} + +header_list='content-type;host;x-amz-content-sha256;x-amz-date;x-amz-server-side-encryption;x-amz-storage-class' + +canonical_request="\ +${request_type} +/${VORTEX_UPLOAD_DB_S3_PREFIX}${VORTEX_UPLOAD_DB_S3_REMOTE_FILE} + +content-type:${content_type} +host:${VORTEX_UPLOAD_DB_S3_BUCKET}${base_url} +x-amz-content-sha256:${payload_hash} +x-amz-date:${date_long} +x-amz-server-side-encryption:AES256 +x-amz-storage-class:${VORTEX_UPLOAD_DB_S3_STORAGE_CLASS} + +${header_list} +${payload_hash}" + +canonical_request_hash=$(printf '%s' "${canonical_request}" | openssl dgst -sha256 -hex 2>/dev/null | sed 's/^.* //') + +string_to_sign="\ +${auth_type} +${date_long} +${date_short}/${VORTEX_UPLOAD_DB_S3_REGION}/${service}/aws4_request +${canonical_request_hash}" + +signature=$(aws_sign4 "${VORTEX_UPLOAD_DB_S3_SECRET_KEY}" "${date_short}" "${VORTEX_UPLOAD_DB_S3_REGION}" "${service}" "${string_to_sign}") + +curl --silent --location --proto-redir =https --request "${request_type}" --upload-file "${local_file}" \ + --header "Content-Type: ${content_type}" \ + --header "Host: ${VORTEX_UPLOAD_DB_S3_BUCKET}${base_url}" \ + --header "X-Amz-Content-SHA256: ${payload_hash}" \ + --header "X-Amz-Date: ${date_long}" \ + --header "X-Amz-Server-Side-Encryption: AES256" \ + --header "X-Amz-Storage-Class: ${VORTEX_UPLOAD_DB_S3_STORAGE_CLASS}" \ + --header "Authorization: ${auth_type} Credential=${VORTEX_UPLOAD_DB_S3_ACCESS_KEY}/${date_short}/${VORTEX_UPLOAD_DB_S3_REGION}/${service}/aws4_request, SignedHeaders=${header_list}, Signature=${signature}" \ + "${object_url}" + +pass "Finished database dump upload to S3." diff --git a/tests/behat/bootstrap/FeatureContext.php b/tests/behat/bootstrap/FeatureContext.php index 50ffbd9b..6cebe5f7 100644 --- a/tests/behat/bootstrap/FeatureContext.php +++ b/tests/behat/bootstrap/FeatureContext.php @@ -19,6 +19,7 @@ use DrevOps\BehatSteps\Drupal\FileTrait; use DrevOps\BehatSteps\Drupal\MediaTrait; use DrevOps\BehatSteps\Drupal\MenuTrait; +use DrevOps\BehatSteps\MetatagTrait; use DrevOps\BehatSteps\Drupal\OverrideTrait; use DrevOps\BehatSteps\Drupal\ParagraphsTrait; use DrevOps\BehatSteps\Drupal\SearchApiTrait; @@ -32,7 +33,6 @@ use DrevOps\BehatSteps\JavascriptTrait; use DrevOps\BehatSteps\KeyboardTrait; use DrevOps\BehatSteps\LinkTrait; -use DrevOps\BehatSteps\MetatagTrait; use DrevOps\BehatSteps\PathTrait; use DrevOps\BehatSteps\ResponseTrait; use DrevOps\BehatSteps\WaitTrait; diff --git a/tests/phpunit/Drupal/EnvironmentSettingsTest.php b/tests/phpunit/Drupal/EnvironmentSettingsTest.php index 6e123b23..3d4b2b9c 100644 --- a/tests/phpunit/Drupal/EnvironmentSettingsTest.php +++ b/tests/phpunit/Drupal/EnvironmentSettingsTest.php @@ -260,8 +260,6 @@ public function testEnvironmentNoOverrides(): void { $config['environment_indicator.settings']['toolbar_integration'] = [TRUE]; $config['robotstxt.settings']['content'] = "User-agent: *\nDisallow: /"; $config['shield.settings']['shield_enable'] = TRUE; - $config['shield.settings']['method'] = 0; - $config['shield.settings']['paths'] = '/.well-known/acme-challenge/*'; $config['system.performance']['cache']['page']['max_age'] = 900; $this->assertConfig($config); @@ -335,8 +333,6 @@ public function testEnvironmentOverrides(): void { $config['environment_indicator.settings']['toolbar_integration'] = [TRUE]; $config['robotstxt.settings']['content'] = "User-agent: *\nDisallow: /"; $config['shield.settings']['shield_enable'] = TRUE; - $config['shield.settings']['method'] = 0; - $config['shield.settings']['paths'] = '/.well-known/acme-challenge/*'; $config['system.performance']['cache']['page']['max_age'] = 1800; $this->assertConfig($config); @@ -386,8 +382,6 @@ public function testEnvironmentLocal(): void { $config['purge_control.settings']['purge_auto_control'] = FALSE; $config['robotstxt.settings']['content'] = "User-agent: *\nDisallow: /"; $config['shield.settings']['shield_enable'] = FALSE; - $config['shield.settings']['method'] = 0; - $config['shield.settings']['paths'] = '/.well-known/acme-challenge/*'; $config['system.logging']['error_level'] = 'all'; $config['system.performance']['cache']['page']['max_age'] = 900; $config['seckit.settings']['seckit_xss']['csp']['upgrade-req'] = FALSE; @@ -439,8 +433,6 @@ public function testEnvironmentLocalContainer(): void { $config['purge_control.settings']['purge_auto_control'] = FALSE; $config['robotstxt.settings']['content'] = "User-agent: *\nDisallow: /"; $config['shield.settings']['shield_enable'] = FALSE; - $config['shield.settings']['method'] = 0; - $config['shield.settings']['paths'] = '/.well-known/acme-challenge/*'; $config['system.logging']['error_level'] = 'all'; $config['system.performance']['cache']['page']['max_age'] = 900; $config['seckit.settings']['seckit_xss']['csp']['upgrade-req'] = FALSE; @@ -494,8 +486,6 @@ public function testEnvironmentGha(): void { $config['purge_control.settings']['purge_auto_control'] = FALSE; $config['robotstxt.settings']['content'] = "User-agent: *\nDisallow: /"; $config['shield.settings']['shield_enable'] = FALSE; - $config['shield.settings']['method'] = 0; - $config['shield.settings']['paths'] = '/.well-known/acme-challenge/*'; $config['system.logging']['error_level'] = 'all'; $config['system.performance']['cache']['page']['max_age'] = 900; $config['seckit.settings']['seckit_xss']['csp']['upgrade-req'] = FALSE; @@ -549,8 +539,6 @@ public function testEnvironmentLagoonPreview(): void { $config['environment_indicator.settings']['toolbar_integration'] = [TRUE]; $config['robotstxt.settings']['content'] = "User-agent: *\nDisallow: /"; $config['shield.settings']['shield_enable'] = TRUE; - $config['shield.settings']['method'] = 0; - $config['shield.settings']['paths'] = '/.well-known/acme-challenge/*'; $config['system.performance']['cache']['page']['max_age'] = 900; $this->assertConfig($config); @@ -605,8 +593,6 @@ public function testEnvironmentLagoonDev(): void { $config['environment_indicator.settings']['toolbar_integration'] = [TRUE]; $config['robotstxt.settings']['content'] = "User-agent: *\nDisallow: /"; $config['shield.settings']['shield_enable'] = TRUE; - $config['shield.settings']['method'] = 0; - $config['shield.settings']['paths'] = '/.well-known/acme-challenge/*'; $config['system.performance']['cache']['page']['max_age'] = 900; $this->assertConfig($config); @@ -661,8 +647,6 @@ public function testEnvironmentLagoonTest(): void { $config['environment_indicator.settings']['toolbar_integration'] = [TRUE]; $config['robotstxt.settings']['content'] = "User-agent: *\nDisallow: /"; $config['shield.settings']['shield_enable'] = TRUE; - $config['shield.settings']['method'] = 0; - $config['shield.settings']['paths'] = '/.well-known/acme-challenge/*'; $config['system.performance']['cache']['page']['max_age'] = 900; $this->assertConfig($config); @@ -715,8 +699,6 @@ public function testEnvironmentLagoonProd(): void { $config['environment_indicator.indicator']['name'] = self::ENVIRONMENT_PROD; $config['environment_indicator.settings']['favicon'] = TRUE; $config['environment_indicator.settings']['toolbar_integration'] = [TRUE]; - $config['shield.settings']['method'] = 0; - $config['shield.settings']['paths'] = '/.well-known/acme-challenge/*'; $config['system.performance']['cache']['page']['max_age'] = 900; $config['system.performance']['css']['preprocess'] = TRUE; $config['system.performance']['js']['preprocess'] = TRUE; diff --git a/tests/phpunit/Drupal/SettingsTestCase.php b/tests/phpunit/Drupal/SettingsTestCase.php index dc703539..737274de 100644 --- a/tests/phpunit/Drupal/SettingsTestCase.php +++ b/tests/phpunit/Drupal/SettingsTestCase.php @@ -199,7 +199,7 @@ protected function unsetEnvVars(): void { /** * Require settings file. */ - protected function requireSettingsFile(): void { + protected function requireSettingsFile(array $pre_settings = []): void { $app_root = getcwd() . '/web'; if (!file_exists($app_root)) { @@ -208,7 +208,7 @@ protected function requireSettingsFile(): void { $site_path = 'sites/default'; $config = []; - $settings = []; + $settings = $pre_settings; $databases = []; require $app_root . DIRECTORY_SEPARATOR . $site_path . DIRECTORY_SEPARATOR . 'settings.php'; diff --git a/tests/phpunit/Drupal/SwitchableSettingsTest.php b/tests/phpunit/Drupal/SwitchableSettingsTest.php index fb6feece..7964175e 100644 --- a/tests/phpunit/Drupal/SwitchableSettingsTest.php +++ b/tests/phpunit/Drupal/SwitchableSettingsTest.php @@ -529,6 +529,90 @@ public static function dataProviderShield(): array { 'shield.settings' => ['shield_enable' => FALSE], ], ], + + // ACME challenge passthrough tests. + [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 1, + ], + [ + 'shield.settings' => [ + 'shield_enable' => TRUE, + 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], + 'method' => 0, + 'paths' => '/.well-known/acme-challenge/*', + ], + ], + ], + [ + self::ENVIRONMENT_LOCAL, + [ + 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 1, + ], + [ + 'shield.settings' => [ + 'shield_enable' => FALSE, + 'method' => 0, + 'paths' => '/.well-known/acme-challenge/*', + ], + ], + ], + // ACME challenge disabled - verify settings are absent. + [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + ], + [ + 'shield.settings' => [ + 'shield_enable' => TRUE, + 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], + ], + ], + [ + 'shield.settings' => ['method' => NULL, 'paths' => NULL], + ], + ], + // ACME challenge with empty value - should not set. + [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => '', + ], + [ + 'shield.settings' => [ + 'shield_enable' => TRUE, + 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], + ], + ], + [ + 'shield.settings' => ['method' => NULL, 'paths' => NULL], + ], + ], + // ACME challenge with 0 value - should not set. + [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 0, + ], + [ + 'shield.settings' => [ + 'shield_enable' => TRUE, + 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], + ], + ], + [ + 'shield.settings' => ['method' => NULL, 'paths' => NULL], + ], + ], ]; } diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php new file mode 100644 index 00000000..3b9dcedc --- /dev/null +++ b/tests/phpunit/bootstrap.php @@ -0,0 +1,28 @@ +