diff --git a/.env b/.env index 559cad584..26d3f6eed 100644 --- a/.env +++ b/.env @@ -374,7 +374,7 @@ VORTEX_NOTIFY_WEBHOOK_URL= #;< DB_DOWNLOAD_SOURCE_URL # URL of the database used for demonstration with URL database download type. -VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/25.4.0/db_d11.demo.sql +VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/1.37.0/db.demo.sql #;> DB_DOWNLOAD_SOURCE_URL #;< MIGRATION @@ -390,6 +390,6 @@ VORTEX_DOWNLOAD_DB2_URL=https://github.com/drevops/vortex/releases/download/25.4 # The line below will be automatically uncommented for database-in-image # storage. It is commented out to allow running non-database-in-image # workflow by default. -##### VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest +##### VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-11.x:1.38.0-rc1 #;> DB_DOWNLOAD_SOURCE_CONTAINER_REGISTRY #;> DEMO_MODE diff --git a/.github/workflows/build-test-deploy.yml b/.github/workflows/build-test-deploy.yml index 61cce049e..c7b564d69 100644 --- a/.github/workflows/build-test-deploy.yml +++ b/.github/workflows/build-test-deploy.yml @@ -488,6 +488,7 @@ jobs: - name: Upload coverage report to Codecov uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6 if: ${{ (matrix.instance == 0 || strategy.job-total == 1) && env.CODECOV_TOKEN != '' }} + continue-on-error: true with: directory: .logs/coverage fail_ci_if_error: true diff --git a/.github/workflows/vortex-test-common.yml b/.github/workflows/vortex-test-common.yml index ee7c54ae5..747012d78 100644 --- a/.github/workflows/vortex-test-common.yml +++ b/.github/workflows/vortex-test-common.yml @@ -110,6 +110,7 @@ jobs: - name: Upload coverage report to Codecov uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6 + continue-on-error: true with: directory: /tmp/.vortex-coverage-html fail_ci_if_error: false @@ -216,6 +217,7 @@ jobs: - name: Upload coverage report to Codecov uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6 + continue-on-error: true with: directory: /tmp/.vortex-coverage-html fail_ci_if_error: false diff --git a/.github/workflows/vortex-test-docs.yml b/.github/workflows/vortex-test-docs.yml index b8d4c7ff3..627f4a40c 100644 --- a/.github/workflows/vortex-test-docs.yml +++ b/.github/workflows/vortex-test-docs.yml @@ -108,6 +108,7 @@ jobs: - name: Upload coverage report to Codecov uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6 + continue-on-error: true with: files: .vortex/docs/.logs/cobertura.xml fail_ci_if_error: true diff --git a/.github/workflows/vortex-test-installer.yml b/.github/workflows/vortex-test-installer.yml index 5d9dd2c4b..118b217e1 100644 --- a/.github/workflows/vortex-test-installer.yml +++ b/.github/workflows/vortex-test-installer.yml @@ -80,6 +80,7 @@ jobs: - name: Upload coverage report to Codecov uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6 + continue-on-error: true with: files: .vortex/installer/.logs/cobertura.xml fail_ci_if_error: true diff --git a/.vortex/docs/content/contributing/maintenance/template.mdx b/.vortex/docs/content/contributing/maintenance/template.mdx index be25714e1..d0788e1f5 100644 --- a/.vortex/docs/content/contributing/maintenance/template.mdx +++ b/.vortex/docs/content/contributing/maintenance/template.mdx @@ -234,188 +234,58 @@ bats .vortex/tests/bats/deploy.bats ## Updating test assets -There are *demo* and *test* database dumps captured as *files* and *container images*. +There are *demo* and *test* database dumps captured as *files* and *container +images*. All flows are automated by `.vortex/tests/update-test-assets`, which +prints every shell command it runs. -### Updating *demo* database dump *file* - -
-Show instructions - -1. Run fresh build of **Vortex** locally: -```shell -rm .data/db.sql || true -VORTEX_PROVISION_TYPE=profile VORTEX_PROVISION_POST_OPERATIONS_SKIP=1 AHOY_CONFIRM_RESPONSE=1 ahoy build -``` -2. Update content and config: -```shell -ahoy cli - -drush eval "Drupal::entityTypeManager()->getStorage('node')->create([ - 'type' => 'page', - 'title' => 'Welcome to the demo site!', - 'body' => [ - 'value' => '

This demo page is sourced from the Vortex database dump file to demonstrate database importing capabilities.

', - 'format' => 'basic_html', - ], -])->save();" - -drush config:set system.site page.front "/node/1" -y -drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog" - -exit - -``` -3. Export DB: -```shell -ahoy export-db db.demo.sql -``` -4. Upload `db.demo.sql` to the latest release as an asset and name it `db_d11.demo.sql`. - -
- -### Updating *demo* database *container image* - -
-Show instructions - -1. Run fresh build of **Vortex** locally: ```shell -rm .data/db.sql || true -VORTEX_PROVISION_TYPE=profile VORTEX_PROVISION_POST_OPERATIONS_SKIP=1 AHOY_CONFIRM_RESPONSE=1 ahoy build +php .vortex/tests/update-test-assets [mode] [--tag ] ``` -2. Update content and config: -```shell -ahoy cli -drush eval "Drupal::entityTypeManager()->getStorage('node')->create([ - 'type' => 'page', - 'title' => 'Welcome to the demo site!', - 'body' => [ - 'value' => '

This demo page is sourced from the Vortex database container image to demonstrate database importing capabilities.

', - 'format' => 'basic_html', - ], -])->save();" +Without arguments, runs `all` for a full refresh. Default tag is `latest`. -drush config:set system.site page.front "/node/1" -y -drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog" +### Modes -exit +| Mode | What it does | Follow-up | +| --- | --- | --- | +| `demo-dump` | Builds the demo profile in-place; exports `.data/db.demo.sql`. | Upload as `db.demo.sql` to the latest GitHub release. | +| `demo-image` | Builds the demo profile in-place; pushes `drevops/vortex-dev-mariadb-drupal-data-demo-11.x:`. | None. | +| `test-dump` | Installs Vortex into `/tmp/star-wars`; builds; exports `/tmp/star-wars/.data/db.test.sql`. | Upload as `db_d11.test.sql` to the latest GitHub release. | +| `test-image` | Installs Vortex into `/tmp/star-wars`; builds; pushes `drevops/vortex-dev-mariadb-drupal-data-test-11.x:`. | None. | +| `destination-images` | Tags and pushes the local demo image to the didi destination tags (`vortex-dev-database-ii`, `vortex-dev-didi-database-fi`). | None. | +| `all` | Optimised full refresh: builds the demo stack once for both demo modes and the test stack once for both test modes (2 stack builds instead of 4), then pushes images and tags destination images. Default when no mode is given. | Upload both dump files as above. | -``` -3. Export DB: -```shell -ahoy export-db db.demo_image.sql +### Options -# Update the collation to avoid issues with MariaDB 10.5+: -sed -i '' 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' .data/db.demo_image.sql -``` -4. Seed the database container image: -```shell -curl -LO https://github.com/drevops/mariadb-drupal-data/releases/latest/download/seed.sh -chmod +x seed.sh -./seed.sh .data/db.demo_image.sql drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest -``` - -
- -### Updating *test* database dump *file* - -
-Show instructions +| Option | Description | +| --- | --- | +| `--tag ` | Image tag for image modes. Default: `latest`. Also accepts `--tag=`. | -1. Run a fresh install of **Vortex** into a new directory and name the project `Star Wars`: -```shell -mkdir /tmp/star-wars -VORTEX_INSTALLER_TEMPLATE_REPO="$(pwd)" .vortex/installer/installer.php /tmp/star-wars --no-interaction -cd /tmp/star-wars -``` -2. Run fresh build of **Vortex** locally: -```shell -rm .data/db.sql || true -VORTEX_PROVISION_TYPE=profile AHOY_CONFIRM_RESPONSE=1 ahoy build -``` -3. Update content and config: -```shell -ahoy cli - -drush eval "Drupal::entityTypeManager()->getStorage('node')->create([ - 'type' => 'page', - 'title' => 'Welcome to the test site!', - 'body' => [ - 'value' => '

This test page is sourced from the Vortex database dump file to demonstrate database importing capabilities.

', - 'format' => 'basic_html', - ], -])->save();" - -drush config:set system.site page.front "/node/1" -y -drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog" - -exit +### Requirements -``` -4. Export DB: -```shell -ahoy export-db db.test.sql -``` -5. Upload `db.test.sql` to the latest release as an asset and name it `db_d11.test.sql`. +- `docker`, `ahoy`, `curl`, `sed`, `php` available on `PATH`. +- A Docker Hub session with push permission to `drevops/` for any image mode. -
+### Caveats -### Updating *test* database *container image* +- Demo modes are **destructive** against the current repository: containers + are reset and `.data/db.sql` is removed before the rebuild. +- Test modes wipe and recreate `/tmp/star-wars` on each run. +- `destination-images` expects `drevops/vortex-dev-mariadb-drupal-data-demo-11.x:1.38.0-rc1` + to already exist locally - run `demo-image` (or `all`) first. -
-Show instructions +### Examples -1. Run a fresh install of **Vortex** into a new directory and name the project `Star Wars`: ```shell -mkdir /tmp/star-wars -VORTEX_INSTALLER_TEMPLATE_REPO="$(pwd)" .vortex/installer/installer.php /tmp/star-wars --no-interaction -cd /tmp/star-wars -``` -2. Run fresh build of **Vortex** locally: -```shell -rm .data/db.sql || true -VORTEX_PROVISION_TYPE=profile AHOY_CONFIRM_RESPONSE=1 ahoy build -``` -3. Update content and config: -```shell -ahoy cli - -drush eval "Drupal::entityTypeManager()->getStorage('node')->create([ - 'type' => 'page', - 'title' => 'Welcome to the test site!', - 'body' => [ - 'value' => '

This test page is sourced from the Vortex database container image to demonstrate database importing capabilities.

', - 'format' => 'basic_html', - ], -])->save();" +# Full refresh of every asset at :latest (default). +php .vortex/tests/update-test-assets -drush config:set system.site page.front "/node/1" -y -drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog" - -exit - -``` -4. Export DB: -```shell -ahoy export-db db.test_image.sql +# Full refresh, tagging images as :rc1. +php .vortex/tests/update-test-assets --tag rc1 -# Update the collation to avoid issues with MariaDB 10.5+: -sed -i '' 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' .data/db.test_image.sql -``` -5. Seed the database container image: -```shell -curl -LO https://github.com/drevops/mariadb-drupal-data/releases/latest/download/seed.sh -chmod +x seed.sh -./seed.sh .data/db.test_image.sql drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest -``` -6. Update destination container images: -```shell -docker tag drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x:vortex-dev-database-ii -docker push drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x:vortex-dev-database-ii +# Refresh only the test container image at :latest. +php .vortex/tests/update-test-assets test-image -docker tag drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x:vortex-dev-didi-database-fi -docker push drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x:vortex-dev-didi-database-fi +# Refresh only the test container image at :rc1. +php .vortex/tests/update-test-assets test-image --tag rc1 ``` - -
diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.env b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.env index 8e379075a..676f0d2db 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.env @@ -225,4 +225,4 @@ VORTEX_NOTIFY_EMAIL_RECIPIENTS="webmaster@star-wars.com|Webmaster" ################################################################################ # URL of the database used for demonstration with URL database download type. -VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/code_coverage_provider_codecov/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/code_coverage_provider_codecov/.github/workflows/build-test-deploy.yml index e6e1b1232..84ebf615d 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/code_coverage_provider_codecov/.github/workflows/build-test-deploy.yml +++ b/.vortex/installer/tests/Fixtures/handler_process/code_coverage_provider_codecov/.github/workflows/build-test-deploy.yml @@ -1,10 +1,11 @@ -@@ -432,6 +432,16 @@ +@@ -432,6 +432,17 @@ hide_and_recreate: true + - name: Upload coverage report to Codecov + uses: codecov/codecov-action@__HASH__ # __VERSION__ + if: ${{ (matrix.instance == 0 || strategy.job-total == 1) && env.CODECOV_TOKEN != '' }} ++ continue-on-error: true + with: + directory: .logs/coverage + fail_ci_if_error: true diff --git a/.vortex/installer/tests/Fixtures/handler_process/db_download_source_acquia/.env b/.vortex/installer/tests/Fixtures/handler_process/db_download_source_acquia/.env index 59cd30f46..0ad8b1c11 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/db_download_source_acquia/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/db_download_source_acquia/.env @@ -30,4 +30,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/db_download_source_container_registry/.env b/.vortex/installer/tests/Fixtures/handler_process/db_download_source_container_registry/.env index a6e94b66b..952c1c0bb 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/db_download_source_container_registry/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/db_download_source_container_registry/.env @@ -32,4 +32,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/db_download_source_ftp/.env b/.vortex/installer/tests/Fixtures/handler_process/db_download_source_ftp/.env index ca9234051..8c94c9cd2 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/db_download_source_ftp/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/db_download_source_ftp/.env @@ -40,4 +40,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/db_download_source_lagoon/.env b/.vortex/installer/tests/Fixtures/handler_process/db_download_source_lagoon/.env index 997c9ca82..1f307068c 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/db_download_source_lagoon/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/db_download_source_lagoon/.env @@ -30,4 +30,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/db_download_source_s3/.env b/.vortex/installer/tests/Fixtures/handler_process/db_download_source_s3/.env index 8f477d4eb..ceabad24f 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/db_download_source_s3/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/db_download_source_s3/.env @@ -37,4 +37,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/.env b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/.env index 2b371977c..e7bcb4010 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/.env @@ -72,4 +72,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_lagoon/.env b/.vortex/installer/tests/Fixtures/handler_process/hosting_lagoon/.env index b03fffd2d..8565b1025 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/hosting_lagoon/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_lagoon/.env @@ -58,4 +58,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/.env b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/.env index d43b77eda..0229211a3 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/.env @@ -72,4 +72,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___lagoon/.env b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___lagoon/.env index 617791567..92128ac31 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___lagoon/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___lagoon/.env @@ -58,4 +58,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_lagoon/.env b/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_lagoon/.env index b03fffd2d..8565b1025 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_lagoon/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_lagoon/.env @@ -58,4 +58,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_url/.env b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_url/.env index ac309d232..c695f08c8 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_url/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_url/.env @@ -31,7 +31,7 @@ @@ -226,3 +249,6 @@ # URL of the database used for demonstration with URL database download type. - VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql + VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql + +# URL of the migration source database used for demonstration. +VORTEX_DOWNLOAD_DB2_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo_source.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled/.env b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled/.env index ac309d232..c695f08c8 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled/.env @@ -31,7 +31,7 @@ @@ -226,3 +249,6 @@ # URL of the database used for demonstration with URL database download type. - VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql + VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql + +# URL of the migration source database used for demonstration. +VORTEX_DOWNLOAD_DB2_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo_source.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_circleci/.env b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_circleci/.env index ac309d232..c695f08c8 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_circleci/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_circleci/.env @@ -31,7 +31,7 @@ @@ -226,3 +249,6 @@ # URL of the database used for demonstration with URL database download type. - VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql + VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql + +# URL of the migration source database used for demonstration. +VORTEX_DOWNLOAD_DB2_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo_source.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.env b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.env index d7392e83b..b63aa8ab4 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.env @@ -83,4 +83,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/provision_database_lagoon/.env b/.vortex/installer/tests/Fixtures/handler_process/provision_database_lagoon/.env index b03fffd2d..8565b1025 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/provision_database_lagoon/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/provision_database_lagoon/.env @@ -58,4 +58,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/provision_profile/.env b/.vortex/installer/tests/Fixtures/handler_process/provision_profile/.env index c43ff35c7..77f5f4583 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/provision_profile/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/provision_profile/.env @@ -46,4 +46,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/starter_drupal_cms_profile/.env b/.vortex/installer/tests/Fixtures/handler_process/starter_drupal_cms_profile/.env index 7acececa1..185cfcde2 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/starter_drupal_cms_profile/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/starter_drupal_cms_profile/.env @@ -24,4 +24,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/starter_drupal_profile/.env b/.vortex/installer/tests/Fixtures/handler_process/starter_drupal_profile/.env index a6741341d..4dc376352 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/starter_drupal_profile/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/starter_drupal_profile/.env @@ -15,4 +15,4 @@ -################################################################################ - -# URL of the database used for demonstration with URL database download type. --VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql +-VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql diff --git a/.vortex/installer/tests/Fixtures/handler_process/theme_custom_non_vortex/.env b/.vortex/installer/tests/Fixtures/handler_process/theme_custom_non_vortex/.env index 87210e92f..b6b1464cc 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/theme_custom_non_vortex/.env +++ b/.vortex/installer/tests/Fixtures/handler_process/theme_custom_non_vortex/.env @@ -3,7 +3,7 @@ # # HTTP Basic Authentication credentials should be embedded into the value. -VORTEX_DOWNLOAD_DB_URL= -+VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db_d11.demo.sql ++VORTEX_DOWNLOAD_DB_URL=https://github.com/drevops/vortex/releases/download/__VERSION__/db.demo.sql # Environment to download the database from. # diff --git a/.vortex/tests/bats/unit/provision.bats b/.vortex/tests/bats/unit/provision.bats index 6656a3803..068ccc83b 100644 --- a/.vortex/tests/bats/unit/provision.bats +++ b/.vortex/tests/bats/unit/provision.bats @@ -1191,7 +1191,7 @@ assert_provision_info() { rm -f ./scripts/custom/provision-20-migration.sh export CI=1 - export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest" + export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-11.x:1.38.0-rc1" mkdir "./.data" touch "./.data/db.sql" @@ -1239,7 +1239,7 @@ assert_provision_info() { export CI=1 export VORTEX_PROVISION_SANITIZE_DB_PASSWORD="MOCK_DB_SANITIZE_PASSWORD" - export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest" + export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-11.x:1.38.0-rc1" mkdir "./.data" touch "./.data/db.sql" @@ -1380,7 +1380,7 @@ assert_provision_info() { export CI=1 export VORTEX_PROVISION_SANITIZE_DB_PASSWORD="MOCK_DB_SANITIZE_PASSWORD" - export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest" + export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-11.x:1.38.0-rc1" mkdir "./.data" touch "./.data/db.sql" @@ -1585,7 +1585,7 @@ assert_provision_info() { rm -f ./scripts/custom/provision-20-migration.sh export CI=1 - export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest" + export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-11.x:1.38.0-rc1" mkdir "./.data" touch "./.data/db.sql" diff --git a/.vortex/tests/phpunit/Functional/AhoyWorkflowTest.php b/.vortex/tests/phpunit/Functional/AhoyWorkflowTest.php index 8c7c6d8d1..6880b99be 100644 --- a/.vortex/tests/phpunit/Functional/AhoyWorkflowTest.php +++ b/.vortex/tests/phpunit/Functional/AhoyWorkflowTest.php @@ -150,7 +150,7 @@ public function testAhoyWorkflowDatabaseFromImageStorageInImage(): void { $this->assertFileContainsString('.env', 'VORTEX_DOWNLOAD_DB_SOURCE=container_registry', '.env should contain container registry source'); $this->assertFileContainsString('.env', 'VORTEX_DB_IMAGE=' . self::VORTEX_DB_IMAGE_TEST, '.env should contain correct database image'); // Assert that demo config was removed as a part of the installation. - $this->assertFileNotContainsString('.env', 'VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest', '.env should not contain demo database image'); + $this->assertFileNotContainsString('.env', 'VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-11.x:1.38.0-rc1', '.env should not contain demo database image'); $this->assertFileNotContainsString('.env', 'VORTEX_DOWNLOAD_DB_URL=', '.env should not contain database download URL'); // Do not use demo database - testing demo database discovery is @@ -498,6 +498,12 @@ public function testAhoyWorkflowProvisionFallbackToProfile(): void { $this->logSubstep('Drop the database to simulate a fresh environment'); $this->cmd('ahoy drush sql:drop -y', txt: 'Database should be dropped successfully'); + // Vortex's Redis settings put 'cache.container' on Redis, so the Drupal + // kernel reuses the cached container (built from the pre-drop module set) + // even after the DB is wiped. Flush Redis to force the kernel to rebuild + // its container from the freshly-installed modules. + $this->cmd('ahoy flush-redis', txt: 'Redis cache flushed so the kernel rebuilds its container'); + $this->logSubstep('Provision without fallback should fail'); $this->fileAddVar('.env', 'VORTEX_PROVISION_FALLBACK_TO_PROFILE', 0); $this->syncToContainer(['.env']); @@ -511,6 +517,9 @@ public function testAhoyWorkflowProvisionFallbackToProfile(): void { 'Provision without fallback should fail when no database dump is available', ); + $logs_dir = self::locationsRoot() . '/.vortex/tests/.logs'; + @mkdir($logs_dir, 0777, TRUE); + $this->logSubstep('Provision with fallback should succeed'); $this->fileAddVar('.env', 'VORTEX_PROVISION_FALLBACK_TO_PROFILE', 1); $this->syncToContainer(['.env']); @@ -527,9 +536,40 @@ public function testAhoyWorkflowProvisionFallbackToProfile(): void { tio: 15 * 60, ); + // DEBUG: capture the full output of the fallback provision run before any + // further probes run drush and rebuild the container. + file_put_contents( + $logs_dir . '/debug-00-provision-output.txt', + sprintf("--- STDOUT ---\n%s\n--- STDERR ---\n%s\n", $this->processGet()->getOutput(), $this->processGet()->getErrorOutput()) + ); + $this->logSubstep('Assert that Shield module is enabled'); $this->cmd('ahoy drush pm:list --status=enabled --type=module --format=list', '* shield', 'Shield module should be enabled after fallback provision'); + // Diagnostic probes to understand why the homepage hits the redirect + // module after a fallback profile install. Each probe writes its full + // output to '.logs/debug-.txt' so it ships out as a test artifact + // (cmd()'s streaming output is suppressed in CI). Multi-word args route + // through 'ahoy cli' with a single-quoted outer arg so they survive + // ahoy's '$*' expansion intact. + // @todo Remove once the underlying cause is understood and fixed. + $this->captureProbe($logs_dir, '01-enabled-modules.txt', "ahoy cli 'vendor/bin/drush pm:list --status=enabled --type=module --format=list'"); + $this->captureProbe($logs_dir, '02-core-extension.txt', "ahoy cli 'vendor/bin/drush config:get core.extension'"); + $this->captureProbe($logs_dir, '03-redirect-module-status.txt', "ahoy cli 'vendor/bin/drush pm:list --filter=redirect --format=list || true'"); + $this->captureProbe($logs_dir, '04-show-tables.txt', "ahoy cli 'vendor/bin/drush sql:query \"SHOW TABLES\" || true'"); + $this->captureProbe($logs_dir, '05-redirect-tables.txt', "ahoy cli 'vendor/bin/drush sql:query \"SHOW TABLES LIKE \\\"redirect%\\\"\" || true'"); + $this->captureProbe($logs_dir, '06-cache-container-count.txt', "ahoy cli 'vendor/bin/drush sql:query \"SELECT COUNT(*) FROM cache_container\" || true'"); + $this->captureProbe($logs_dir, '07-phpstorage-ls-php.txt', "ahoy cli 'ls -la web/sites/default/files/php/ 2>/dev/null || echo NO_PHP_DIR'"); + $this->captureProbe($logs_dir, '08-phpstorage-ls-container.txt', "ahoy cli 'ls -la web/sites/default/files/php/container/ 2>/dev/null || echo NO_CONTAINER_DIR'"); + $this->captureProbe($logs_dir, '09-phpstorage-find.txt', "ahoy cli 'find web/sites/default/files/php -type f 2>/dev/null | head -50 || echo NO_FILES'"); + $this->captureProbe($logs_dir, '10-grep-redirect-in-cache.txt', "ahoy cli 'grep -l Drupal..redirect web/sites/default/files/php/container/*.php 2>/dev/null || echo NO_REDIRECT_IN_CONTAINER_CACHE'"); + $this->captureProbe($logs_dir, '11-grep-subscriber-in-cache.txt', "ahoy cli 'grep -c redirect.request_subscriber web/sites/default/files/php/container/*.php 2>/dev/null || echo NO_REDIRECT_SUBSCRIBER_IN_CACHE'"); + $this->captureProbe($logs_dir, '12-watchdog.txt', "ahoy cli 'vendor/bin/drush watchdog:show --count=200 || true'"); + $this->captureProbe($logs_dir, '13-homepage.txt', "ahoy cli 'curl -sS -o /tmp/homepage.html -w HTTP_STATUS=%{http_code} http://nginx:8080/ ; echo ; head -c 4000 /tmp/homepage.html'"); + $this->captureProbe($logs_dir, '14-config-default-listing.txt', "ahoy cli 'ls -la ../config/default/ | head -80'"); + $this->captureProbe($logs_dir, '15-core-extension-yml.txt', "ahoy cli 'cat ../config/default/core.extension.yml 2>/dev/null || echo NO_CORE_EXTENSION_YML'"); + $this->captureProbe($logs_dir, '16-shield-status.txt', "ahoy cli 'vendor/bin/drush pm:list --filter=shield --format=list || true'"); + $this->logSubstep('Assert that homepage does not contain database dump content'); $this->assertWebpageNotContains('/', 'This demo page is sourced from the Vortex database dump file', 'Homepage should not show database dump content after fallback provision'); @@ -537,4 +577,40 @@ public function testAhoyWorkflowProvisionFallbackToProfile(): void { $this->assertWebpageContains('/', 'logSubstep('DEBUG probe -> ' . $filename); + + try { + $process = $this->processRun($command); + $payload = sprintf( + "COMMAND:\n %s\n\nEXIT CODE: %d\n\n--- STDOUT ---\n%s\n--- STDERR ---\n%s\n", + $command, + $process->getExitCode() ?? -1, + $process->getOutput(), + $process->getErrorOutput() + ); + } + catch (\Throwable $exception) { + $payload = sprintf( + "COMMAND:\n %s\n\nEXCEPTION: %s\n", + $command, + $exception->getMessage() + ); + } + + file_put_contents($logs_dir . '/debug-' . $filename, $payload); + } + } diff --git a/.vortex/tests/phpunit/Traits/SutTrait.php b/.vortex/tests/phpunit/Traits/SutTrait.php index f1b387342..1dd073b87 100644 --- a/.vortex/tests/phpunit/Traits/SutTrait.php +++ b/.vortex/tests/phpunit/Traits/SutTrait.php @@ -34,7 +34,7 @@ trait SutTrait { /** * Image name for the test database. */ - const VORTEX_DB_IMAGE_TEST = 'drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest'; + const VORTEX_DB_IMAGE_TEST = 'drevops/vortex-dev-mariadb-drupal-data-test-11.x:1.38.0-rc1'; /** * Environment variables to set when running the installer. diff --git a/.vortex/tests/update-test-assets b/.vortex/tests/update-test-assets new file mode 100755 index 000000000..a1c632635 --- /dev/null +++ b/.vortex/tests/update-test-assets @@ -0,0 +1,555 @@ +#!/usr/bin/env php +] + * + * Without arguments, runs the 'all' mode (full refresh). + * + * Modes: + * demo-dump Build demo in-place; export .data/db.demo.sql. + * demo-image Build demo in-place; push + * drevops/vortex-dev-mariadb-drupal-data-demo-11.x:. + * test-dump Install Vortex into /tmp/star-wars; export + * .data/db.test.sql there. + * test-image Install Vortex into /tmp/star-wars; push + * drevops/vortex-dev-mariadb-drupal-data-test-11.x:. + * destination-images Tag and push the local demo image to didi destination + * tags (vortex-dev-database-ii, vortex-dev-didi-database-fi). + * all Run all of the above (default). Optimised: builds + * the demo stack once for both demo modes and the + * test stack once for both test modes (2 stack builds + * instead of 4). + * + * Options: + * --tag Image tag for image modes (default: 'latest'). + * Also accepts '--tag='. + * + * Demo modes run in-place against the current repository and are destructive: + * they wipe containers and remove '.data/db.sql' before rebuilding. Test modes + * nuke and recreate '/tmp/star-wars'. Image modes require a Docker Hub session + * with push permission to 'drevops/'. + */ + +const SUT_DIR = '/tmp/star-wars'; +const SEED_URL = 'https://github.com/drevops/mariadb-drupal-data/releases/latest/download/seed.sh'; +const IMAGE_DEMO = 'drevops/vortex-dev-mariadb-drupal-data-demo-11.x'; +const IMAGE_TEST = 'drevops/vortex-dev-mariadb-drupal-data-test-11.x'; +const IMAGE_DEST = 'drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x'; + +$valid_modes = ['demo-dump', 'demo-image', 'test-dump', 'test-image', 'destination-images', 'all']; +[$mode, $tag] = parse_args($argv ?? [], $valid_modes); + +$root_dir = dirname(__DIR__, 2); +$installer = $root_dir . '/.vortex/installer/installer.php'; + +step(sprintf('Mode: %s', $mode)); +step(sprintf('Tag: %s', $tag)); +step(sprintf('Source repo: %s', $root_dir)); + +step('Validate environment'); +require_command('docker'); +require_command('ahoy'); +require_command('curl'); +require_command('sed'); +require_command('php'); + +run_mode($mode, $root_dir, $installer, $tag); + +step('Done'); + +// ============================================================================= +// Mode dispatcher. +// ============================================================================= + +function run_mode(string $mode, string $root_dir, string $installer, string $tag): void { + step(sprintf('--- Running mode: %s ---', $mode)); + + match ($mode) { + 'demo-dump' => mode_demo_dump($root_dir), + 'demo-image' => mode_demo_image($root_dir, $tag), + 'test-dump' => mode_test_dump($root_dir, $installer), + 'test-image' => mode_test_image($root_dir, $installer, $tag), + 'destination-images' => mode_destination_images($tag), + 'all' => mode_all($root_dir, $installer, $tag), + }; +} + +// ============================================================================= +// Modes. +// ============================================================================= + +function mode_demo_dump(string $root_dir): void { + $body = body_demo_dump(); + + build_demo_in_place($root_dir); + set_node_content($root_dir, 'Welcome to the demo site!', $body); + finalise_content($root_dir); + export_db($root_dir, 'db.demo.sql', $body); + print_dump_followup($root_dir, 'db.demo.sql', 'db.demo.sql'); +} + +function mode_demo_image(string $root_dir, string $tag): void { + $image = sprintf('%s:%s', IMAGE_DEMO, $tag); + $body = body_demo_image(); + + build_demo_in_place($root_dir); + set_node_content($root_dir, 'Welcome to the demo site!', $body); + finalise_content($root_dir); + export_db($root_dir, 'db.demo_image.sql', $body); + fix_collation($root_dir, 'db.demo_image.sql'); + seed_image($root_dir, 'db.demo_image.sql', $image); +} + +function mode_test_dump(string $root_dir, string $installer): void { + $body = body_test_dump(); + + reset_sut($root_dir, $installer); + build_in($root_dir, SUT_DIR, FALSE); + set_node_content(SUT_DIR, 'Welcome to the test site!', $body); + finalise_content(SUT_DIR); + export_db(SUT_DIR, 'db.test.sql', $body); + print_dump_followup(SUT_DIR, 'db.test.sql', 'db_d11.test.sql'); +} + +function mode_test_image(string $root_dir, string $installer, string $tag): void { + $image = sprintf('%s:%s', IMAGE_TEST, $tag); + $body = body_test_image(); + + reset_sut($root_dir, $installer); + build_in($root_dir, SUT_DIR, FALSE); + set_node_content(SUT_DIR, 'Welcome to the test site!', $body); + finalise_content(SUT_DIR); + export_db(SUT_DIR, 'db.test_image.sql', $body); + fix_collation(SUT_DIR, 'db.test_image.sql'); + seed_image(SUT_DIR, 'db.test_image.sql', $image); +} + +function mode_destination_images(string $tag): void { + $source = sprintf('%s:%s', IMAGE_DEMO, $tag); + + step(sprintf('Pull source demo image %s', $source)); + run(sprintf('docker pull %s', escapeshellarg($source))); + + step('Tag and push destination images from local demo image'); + foreach (['vortex-dev-database-ii', 'vortex-dev-didi-database-fi'] as $dest_tag) { + $dest = sprintf('%s:%s', IMAGE_DEST, $dest_tag); + run(sprintf('docker tag %s %s', escapeshellarg($source), escapeshellarg($dest))); + run(sprintf('docker push %s', escapeshellarg($dest))); + } +} + +/** + * Optimised full refresh: 2 stack builds (demo + test) instead of 4. + * + * Within each site context (demo and test) the stack is built once, content + * is exported as the dump variant, then the same node's body is updated to + * the image variant before exporting and pushing the image. This reuses the + * provisioned stack and skips two 'ahoy build' invocations. + */ +function mode_all(string $root_dir, string $installer, string $tag): void { + // Demo phase: one stack build, two exports. + step('=== Demo phase (single stack build) ==='); + build_demo_in_place($root_dir); + + $demo_dump_body = body_demo_dump(); + set_node_content($root_dir, 'Welcome to the demo site!', $demo_dump_body); + finalise_content($root_dir); + export_db($root_dir, 'db.demo.sql', $demo_dump_body); + print_dump_followup($root_dir, 'db.demo.sql', 'db.demo.sql'); + + $demo_image_body = body_demo_image(); + set_node_content($root_dir, 'Welcome to the demo site!', $demo_image_body); + truncate_caches($root_dir); + export_db($root_dir, 'db.demo_image.sql', $demo_image_body); + fix_collation($root_dir, 'db.demo_image.sql'); + seed_image($root_dir, 'db.demo_image.sql', sprintf('%s:%s', IMAGE_DEMO, $tag)); + + // Test phase: one stack build, two exports. + step('=== Test phase (single stack build) ==='); + reset_sut($root_dir, $installer); + build_in($root_dir, SUT_DIR, FALSE); + + $test_dump_body = body_test_dump(); + set_node_content(SUT_DIR, 'Welcome to the test site!', $test_dump_body); + finalise_content(SUT_DIR); + export_db(SUT_DIR, 'db.test.sql', $test_dump_body); + print_dump_followup(SUT_DIR, 'db.test.sql', 'db_d11.test.sql'); + + $test_image_body = body_test_image(); + set_node_content(SUT_DIR, 'Welcome to the test site!', $test_image_body); + truncate_caches(SUT_DIR); + export_db(SUT_DIR, 'db.test_image.sql', $test_image_body); + fix_collation(SUT_DIR, 'db.test_image.sql'); + seed_image(SUT_DIR, 'db.test_image.sql', sprintf('%s:%s', IMAGE_TEST, $tag)); + + // Destination images. + step('=== Destination images ==='); + mode_destination_images($tag); +} + +// ============================================================================= +// Body texts. +// ============================================================================= + +function body_demo_dump(): string { + return 'This demo page is sourced from the Vortex database dump file to demonstrate database importing capabilities.'; +} + +function body_demo_image(): string { + return 'This demo page is sourced from the Vortex database container image to demonstrate database importing capabilities.'; +} + +function body_test_dump(): string { + return 'This test page is sourced from the Vortex database dump file to demonstrate database importing capabilities.'; +} + +function body_test_image(): string { + return 'This test page is sourced from the Vortex database container image to demonstrate database importing capabilities.'; +} + +function print_dump_followup(string $cwd, string $filename, string $asset_name): void { + step('Manual follow-up'); + println(sprintf(' Upload %s/.data/%s to the latest GitHub release as asset "%s".', $cwd, $filename, $asset_name)); +} + +// ============================================================================= +// Shared subroutines. +// ============================================================================= + +function reset_sut(string $root_dir, string $installer): void { + step(sprintf('Reset SUT directory %s', SUT_DIR)); + + // Drupal stages public files inside containers running as 'www-data' + // (uid 33). When those bind-mounts surface on the host, plain 'rm -rf' + // by the current user fails with "Permission denied". Clear the contents + // via an ephemeral root-privileged container so file ownership doesn't + // matter, then leave the dir itself in place for the installer. + run(sprintf('mkdir -p %s', escapeshellarg(SUT_DIR))); + run(sprintf( + 'docker run --rm -v %s:/sut alpine find /sut -mindepth 1 -delete', + escapeshellarg(SUT_DIR) + )); + + step('Install Vortex into SUT'); + run(sprintf( + 'VORTEX_INSTALLER_TEMPLATE_REPO=%s php %s --no-interaction --destination=%s', + escapeshellarg($root_dir), + escapeshellarg($installer), + escapeshellarg(SUT_DIR) + ), $root_dir); +} + +function build_demo_in_place(string $root_dir): void { + build_in($root_dir, $root_dir, TRUE); +} + +function build_in(string $root_dir, string $cwd, bool $skip_post_ops): void { + step('Remove any pre-existing DB dump'); + run('rm -f .data/db.sql', $cwd); + + // Replicate 'ahoy build' step-by-step so COMPOSER_PROCESS_TIMEOUT=0 can be + // injected into the composer install step (extracting drupal/core can take + // longer than the default 300s on slower hosts). 'ahoy cli' does not forward + // COMPOSER_* env vars from the host, hence the direct 'docker compose exec'. + step('Reset stack'); + run('AHOY_CONFIRM_RESPONSE=1 AHOY_CONFIRM_WAIT_SKIP=1 ahoy reset', $cwd); + + step('Bring up containers'); + run('ahoy up --build --force-recreate', $cwd); + + step('Install Composer dependencies (COMPOSER_PROCESS_TIMEOUT=0)'); + run("docker compose exec -T -e COMPOSER_MEMORY_LIMIT=-1 -e COMPOSER_PROCESS_TIMEOUT=0 cli composer --ansi install", $cwd); + + step('Install front-end dependencies'); + run('ahoy fei', $cwd); + + step('Build front-end assets (skipped if theme not configured)'); + run('ahoy fe || true', $cwd); + + step('Provision'); + $env = $skip_post_ops + ? 'VORTEX_PROVISION_TYPE=profile VORTEX_PROVISION_POST_OPERATIONS_SKIP=1 AHOY_CONFIRM_RESPONSE=1' + : 'VORTEX_PROVISION_TYPE=profile AHOY_CONFIRM_RESPONSE=1'; + run(sprintf('%s ahoy provision', $env), $cwd); + + step('Info'); + run('ahoy info', $cwd); +} + +function set_node_content(string $cwd, string $title, string $body): void { + step(sprintf('Set node 1 content ("%s")', $title)); + + $php = build_set_node_content_php($title, $body); + $drush_cmd = sprintf('drush php:eval %s', escapeshellarg($php)); + $ahoy_cmd = sprintf('ahoy cli %s', escapeshellarg($drush_cmd)); + + run($ahoy_cmd, $cwd); +} + +function finalise_content(string $cwd): void { + set_front_page($cwd, '/node/1'); + truncate_caches($cwd); +} + +function set_front_page(string $cwd, string $path): void { + step(sprintf('Set and verify page.front to %s', $path)); + + $path_escaped = addcslashes($path, '"\\$'); + $php = implode(' ', [ + sprintf('\Drupal::configFactory()->getEditable("system.site")->set("page.front", "%s")->save();', $path_escaped), + sprintf('$front = \Drupal::config("system.site")->get("page.front");'), + sprintf('if ($front !== "%s") { throw new \RuntimeException("page.front mismatch: " . $front); }', $path_escaped), + 'echo "Verified page.front=" . $front . PHP_EOL;', + ]); + $drush_cmd = sprintf('drush php:eval %s', escapeshellarg($php)); + $ahoy_cmd = sprintf('ahoy cli %s', escapeshellarg($drush_cmd)); + + run($ahoy_cmd, $cwd); +} + +function truncate_caches(string $cwd): void { + step('Truncate cache_* tables and watchdog'); + run("ahoy cli 'drush sql:query \"SHOW TABLES LIKE \\\"cache_%\\\"\" | xargs -I{} drush sql:query \"TRUNCATE TABLE {}\" && drush sql:query \"TRUNCATE TABLE watchdog\"'", $cwd); +} + +function export_db(string $cwd, string $filename, ?string $expected_substring = NULL): void { + step(sprintf('Export database to .data/%s', $filename)); + run(sprintf('ahoy export-db %s', escapeshellarg($filename)), $cwd); + + $path = sprintf('%s/.data/%s', $cwd, $filename); + step(sprintf('Verify export file %s', $path)); + + if (!is_file($path)) { + fwrite(STDERR, sprintf("Error: export file %s does not exist.\n", $path)); + exit(1); + } + + $size = filesize($path); + if ($size === FALSE || $size === 0) { + fwrite(STDERR, sprintf("Error: export file %s is empty.\n", $path)); + exit(1); + } + + println(sprintf(' - File exists, %d bytes.', $size)); + + if ($expected_substring !== NULL) { + $contents = file_get_contents($path); + if ($contents === FALSE || !str_contains($contents, $expected_substring)) { + fwrite(STDERR, sprintf("Error: export file %s does not contain expected substring: %s\n", $path, $expected_substring)); + exit(1); + } + println(sprintf(' - Contains expected body text: %s', truncate_for_log($expected_substring, 60))); + } +} + +function fix_collation(string $cwd, string $filename): void { + step('Normalise collation (utf8mb4_0900_ai_ci -> utf8mb4_general_ci)'); + $path = sprintf('.data/%s', $filename); + run(sprintf("sed -i.bak 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' %s", escapeshellarg($path)), $cwd); + run(sprintf('rm -f %s.bak', escapeshellarg($path)), $cwd); + + step('Verify collation rewrite'); + $abs_path = sprintf('%s/.data/%s', $cwd, $filename); + $contents = file_get_contents($abs_path); + if ($contents === FALSE) { + fwrite(STDERR, sprintf("Error: cannot read %s for collation verification.\n", $abs_path)); + exit(1); + } + if (str_contains($contents, 'utf8mb4_0900_ai_ci')) { + fwrite(STDERR, sprintf("Error: %s still contains 'utf8mb4_0900_ai_ci' after rewrite.\n", $abs_path)); + exit(1); + } + println(sprintf(' - No utf8mb4_0900_ai_ci references remain in %s.', $abs_path)); +} + +function truncate_for_log(string $value, int $max): string { + if (strlen($value) <= $max) { + return $value; + } + + return substr($value, 0, $max) . '...'; +} + +function seed_image(string $cwd, string $filename, string $image): void { + step('Download seed.sh'); + run(sprintf('curl -fsSL -o seed.sh %s', escapeshellarg(SEED_URL)), $cwd); + run('chmod +x seed.sh', $cwd); + + step(sprintf('Seed and push image to %s', $image)); + run(sprintf('./seed.sh .data/%s %s', escapeshellarg($filename), escapeshellarg($image)), $cwd); +} + +// ============================================================================= +// Helpers. +// ============================================================================= + +function parse_args(array $argv, array $valid_modes): array { + $mode = NULL; + $tag = 'latest'; + $script = $argv[0] ?? __FILE__; + + $args = array_slice($argv, 1); + $count = count($args); + $i = 0; + + while ($i < $count) { + $arg = $args[$i]; + + if ($arg === '--tag') { + if (!isset($args[$i + 1])) { + usage_error($script, $valid_modes, "Option '--tag' requires a value."); + } + $tag = $args[$i + 1]; + $i += 2; + continue; + } + + if (str_starts_with($arg, '--tag=')) { + $tag = substr($arg, strlen('--tag=')); + if ($tag === '') { + usage_error($script, $valid_modes, "Option '--tag' requires a value."); + } + $i += 1; + continue; + } + + if ($arg === '-h' || $arg === '--help') { + usage_error($script, $valid_modes, NULL, 0); + } + + if (str_starts_with($arg, '-')) { + usage_error($script, $valid_modes, sprintf("Unknown option '%s'.", $arg)); + } + + if ($mode !== NULL) { + usage_error($script, $valid_modes, sprintf("Unexpected positional argument '%s'.", $arg)); + } + + $mode = $arg; + $i += 1; + } + + $mode = $mode ?? 'all'; + + if (!in_array($mode, $valid_modes, TRUE)) { + usage_error($script, $valid_modes, sprintf("Invalid mode '%s'.", $mode)); + } + + return [$mode, $tag]; +} + +function usage_error(string $script, array $valid_modes, ?string $message, int $code = 1): void { + $stream = $code === 0 ? STDOUT : STDERR; + + if ($message !== NULL) { + fwrite($stream, sprintf("Error: %s\n\n", $message)); + } + + fwrite($stream, sprintf("Usage:\n php %s [mode] [--tag ]\n\n", $script)); + fwrite($stream, sprintf("Modes: %s\n", implode(', ', $valid_modes))); + fwrite($stream, "Default mode: all. Default tag: latest.\n"); + + exit($code); +} + +function step(string $message): void { + fwrite(STDOUT, "\n"); + fwrite(STDOUT, sprintf("==> %s\n", $message)); +} + +function println(string $message): void { + fwrite(STDOUT, sprintf("%s\n", $message)); +} + +function require_command(string $cmd): void { + $output = []; + $code = 0; + exec(sprintf('command -v %s 2>/dev/null', escapeshellarg($cmd)), $output, $code); + + if ($code !== 0) { + fwrite(STDERR, sprintf("Error: required command '%s' not found in PATH.\n", $cmd)); + exit(1); + } + + println(sprintf(' - %s: %s', $cmd, $output[0] ?? '(found)')); +} + +function run(string $command, ?string $cwd = NULL): void { + if ($cwd !== NULL) { + println(sprintf(' $ (cd %s) %s', $cwd, $command)); + $command = sprintf('cd %s && %s', escapeshellarg($cwd), $command); + } + else { + println(sprintf(' $ %s', $command)); + } + + passthru($command, $code); + + if ($code !== 0) { + fwrite(STDERR, sprintf("\nError: command failed with exit code %d.\n", $code)); + exit($code); + } +} + +function build_set_node_content_php(string $title, string $body): string { + // Escape characters that have special meaning inside PHP double-quoted + // strings ('"', '\', '$') so the inline 'drush php:eval' payload stays + // intact through host shell, ahoy and container bash. + $title_escaped = addcslashes($title, '"\\$'); + $body_escaped = addcslashes($body, '"\\$'); + + // Idempotent: load node 1 and update it if present, otherwise create a new + // page node. The node MUST be published explicitly - bundle 'page' + // publication defaults are only applied via NodeForm and are NOT inherited + // by entity API create/save, so without an explicit status the node ends up + // unpublished and anonymous visitors get an "Access denied" page. + // If Content Moderation is attached to the bundle (test phase, after + // 'sw_search_deploy_add_editorial_workflow'), 'status' alone is not enough - + // 'moderation_state' must be set to 'published' or the node defaults to + // draft and isPublished() returns false even with status=1. + // Updates skip new revisions to keep exports diff-friendly. + // After save, the node is reloaded and asserted to verify state ended up + // as expected; any mismatch throws and exits non-zero. + return implode(' ', [ + '$storage = \Drupal::entityTypeManager()->getStorage("node");', + '$node = $storage->load(1);', + 'if ($node) {', + sprintf('$node->set("title", "%s");', $title_escaped), + sprintf('$node->set("body", ["value" => "

%s

", "format" => "basic_html"]);', $body_escaped), + '$node->setPublished();', + 'if ($node->hasField("moderation_state")) { $node->set("moderation_state", "published"); }', + '$node->setNewRevision(FALSE);', + '$node->save();', + 'echo "Updated node " . $node->id() . PHP_EOL;', + '} else {', + '$node = $storage->create([', + sprintf('"type" => "page", "title" => "%s",', $title_escaped), + sprintf('"body" => ["value" => "

%s

", "format" => "basic_html"],', $body_escaped), + '"status" => 1,', + '"uid" => 1,', + ']);', + 'if ($node->hasField("moderation_state")) { $node->set("moderation_state", "published"); }', + '$node->save();', + 'echo "Created node " . $node->id() . PHP_EOL;', + '}', + // Reload and verify post-save state. + '$storage->resetCache([1]);', + '$node = $storage->load(1);', + 'if (!$node) { throw new \RuntimeException("Node 1 missing after save."); }', + 'if (!$node->isPublished()) { throw new \RuntimeException("Node 1 is not published."); }', + sprintf('if ($node->getTitle() !== "%s") { throw new \RuntimeException("Title mismatch: " . $node->getTitle()); }', $title_escaped), + sprintf('$expected_body = "

%s

";', $body_escaped), + 'if ($node->get("body")->value !== $expected_body) { throw new \RuntimeException("Body mismatch: " . $node->get("body")->value); }', + '$mod_state = $node->hasField("moderation_state") ? $node->get("moderation_state")->value : "(no moderation)";', + 'echo "Verified node 1: published=1, title=\"" . $node->getTitle() . "\", body length=" . strlen($node->get("body")->value) . ", moderation_state=" . $mod_state . PHP_EOL;', + ]); +}