From 36096a19f89ce33168e9a88da601b61543307c9b Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Tue, 24 Mar 2026 21:08:34 +1100 Subject: [PATCH 1/2] Ported recent shell script changes to PHP tooling package. --- .vortex/tooling/src/deploy-artifact | 24 +++- .vortex/tooling/src/download-db | 59 +++++---- .vortex/tooling/src/download-db-acquia | 23 ++-- .../src/download-db-container-registry | 15 ++- .vortex/tooling/src/download-db-ftp | 17 +-- .vortex/tooling/src/download-db-lagoon | 28 +++-- .vortex/tooling/src/download-db-s3 | 17 +-- .vortex/tooling/src/download-db-url | 11 +- .vortex/tooling/src/notify | 2 +- .vortex/tooling/src/provision | 36 +++++- .../tooling/tests/Unit/DeployArtifactTest.php | 117 +++++++++++------- .vortex/tooling/tests/Unit/ProvisionTest.php | 85 +++++++++++-- 12 files changed, 301 insertions(+), 133 deletions(-) diff --git a/.vortex/tooling/src/deploy-artifact b/.vortex/tooling/src/deploy-artifact index 81b24dc0a..4058d789b 100755 --- a/.vortex/tooling/src/deploy-artifact +++ b/.vortex/tooling/src/deploy-artifact @@ -62,10 +62,18 @@ $deploy_ssh_fingerprint = getenv_default('VORTEX_DEPLOY_ARTIFACT_SSH_FINGERPRINT // Falls back to VORTEX_DEPLOY_SSH_FILE, then VORTEX_SSH_FILE. $deploy_ssh_file = getenv_default('VORTEX_DEPLOY_ARTIFACT_SSH_FILE', 'VORTEX_DEPLOY_SSH_FILE', 'VORTEX_SSH_FILE', getenv('HOME') . '/.ssh/id_rsa'); +// Version of git-artifact to download. +$git_artifact_version = getenv_default('VORTEX_DEPLOY_ARTIFACT_GIT_ARTIFACT_VERSION', '1.4.0'); + +// SHA256 checksum of the git-artifact binary. +$git_artifact_sha256 = getenv_default('VORTEX_DEPLOY_ARTIFACT_GIT_ARTIFACT_SHA256', '1fa99ff2a6f8dc6c1a42bcfc87ce75d04b2eab375216b0e3195a0e3b51a47646'); + // ----------------------------------------------------------------------------- info('Started ARTIFACT deployment.'); +command_must_exist('curl'); + // Configure global git settings, if they do not exist. $global_git_name = trim((string) shell_exec('git config --global user.name')); if (empty($global_git_name)) { @@ -86,7 +94,20 @@ putenv('VORTEX_SSH_PREFIX=DEPLOY_ARTIFACT'); passthru_or_fail(__DIR__ . '/setup-ssh'); task('Installing artifact builder.'); -passthru_or_fail('composer global require --dev -n --ansi --prefer-source --ignore-platform-reqs drevops/git-artifact:~1.2'); +$tmp_dir = getenv('TMPDIR') ?: '/tmp'; +$git_artifact_bin = $tmp_dir . '/git-artifact'; +$git_artifact_url = sprintf('https://github.com/drevops/git-artifact/releases/download/%s/git-artifact', $git_artifact_version); +$dl_response = request($git_artifact_url, ['method' => 'GET', 'save_to' => $git_artifact_bin, 'timeout' => 120]); +if (!$dl_response['ok']) { + fail('Failed to download git-artifact binary.'); +} + +$actual_sha256 = hash_file('sha256', $git_artifact_bin); +if ($actual_sha256 !== $git_artifact_sha256) { + @unlink($git_artifact_bin); + fail('SHA256 checksum verification failed for git-artifact binary.'); +} +chmod($git_artifact_bin, 0755); $deploy_artifact_root = realpath($deploy_artifact_root); $deploy_artifact_src = realpath($deploy_artifact_src); @@ -106,7 +127,6 @@ if (file_exists($deploy_artifact_root . '/.gitignore.artifact')) { } task('Running artifact builder.'); -$git_artifact_bin = getenv('HOME') . '/.composer/vendor/bin/git-artifact'; $git_artifact_args = [ escapeshellarg($git_artifact_bin), escapeshellarg($deploy_git_remote), diff --git a/.vortex/tooling/src/download-db b/.vortex/tooling/src/download-db index 298c9a015..f5825a683 100755 --- a/.vortex/tooling/src/download-db +++ b/.vortex/tooling/src/download-db @@ -19,55 +19,64 @@ execute_override(basename(__FILE__)); // ----------------------------------------------------------------------------- +// Database index suffix. When set (e.g., "2"), all DB-related variable lookups +// use the indexed variant (e.g., VORTEX_DOWNLOAD_DB2_SOURCE instead of +// VORTEX_DOWNLOAD_DB_SOURCE). +$db_index = getenv_default('VORTEX_DB_INDEX', ''); + // Database download source. -$source = getenv_default('VORTEX_DOWNLOAD_DB_SOURCE', 'url'); +$source = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_SOURCE', 'VORTEX_DOWNLOAD_DB_SOURCE', 'url']))); // Force DB download even if the cache exists. -$force = getenv_default('VORTEX_DOWNLOAD_DB_FORCE', ''); +$force = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_FORCE', 'VORTEX_DOWNLOAD_DB_FORCE', '']))); // Proceed with download. -$proceed = getenv_default('VORTEX_DOWNLOAD_DB_PROCEED', '1'); +$proceed = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_PROCEED', 'VORTEX_DOWNLOAD_DB_PROCEED', '1']))); // Database dump file name. -$db_file = getenv_default('VORTEX_DOWNLOAD_DB_FILE', 'VORTEX_DB_FILE', 'db.sql'); +$db_file = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_FILE', 'VORTEX_DB' . $db_index . '_FILE', 'VORTEX_DOWNLOAD_DB_FILE', 'VORTEX_DB_FILE', 'db.sql']))); // Directory with database dump file. -$db_dir = getenv_default('VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data'); +$db_dir = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_DIR', 'VORTEX_DB' . $db_index . '_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data']))); // Semaphore file to indicate download completion. $semaphore = getenv_default('VORTEX_DOWNLOAD_DB_SEMAPHORE', ''); // ----------------------------------------------------------------------------- -info('Started database download.'); +info('Started database%s download.', $db_index !== '' ? ' ' . $db_index : ''); if ($proceed !== '1') { pass('Skipping database download as VORTEX_DOWNLOAD_DB_PROCEED is not set to 1.'); quit(0); } -// Check for existing dump files. -$db_file_basename = pathinfo($db_file, PATHINFO_FILENAME); -$found_db = ''; -if (is_dir($db_dir)) { - $files = glob($db_dir . '/' . $db_file_basename . '.{sql,tar}', GLOB_BRACE); - if ($files !== FALSE && count($files) > 0) { - $found_db = implode("\n", $files); +// Skip file existence check for container_registry source as the database is +// stored as a Docker image, not a file. +if ($source !== 'container_registry') { + // Check for existing dump files. + $db_file_basename = pathinfo($db_file, PATHINFO_FILENAME); + $found_db = ''; + if (is_dir($db_dir)) { + $files = glob($db_dir . '/' . $db_file_basename . '.{sql,tar}', GLOB_BRACE); + if ($files !== FALSE && count($files) > 0) { + $found_db = implode("\n", $files); + } } -} -if ($found_db !== '') { - note('Found existing database dump file(s).'); - passthru('ls -Alh ' . escapeshellarg($db_dir) . ' 2>/dev/null || true'); + if ($found_db !== '') { + note('Found existing database dump file(s).'); + passthru('ls -Alh ' . escapeshellarg($db_dir) . ' 2>/dev/null || true'); - if ($force !== '1') { - note('Using existing database dump file(s).'); - note('Download will not proceed.'); - note('Remove existing database file or set VORTEX_DOWNLOAD_DB_FORCE value to 1 to force download.'); - quit(0); - } + if ($force !== '1') { + note('Using existing database dump file(s).'); + note('Download will not proceed.'); + note('Remove existing database file or set VORTEX_DOWNLOAD_DB_FORCE value to 1 to force download.'); + quit(0); + } - note('Will download a fresh copy of the database.'); + note('Will download a fresh copy of the database.'); + } } $sources = [ @@ -92,4 +101,4 @@ if ($semaphore !== '') { touch($semaphore); } -pass('Finished database download.'); +pass('Finished database%s download.', $db_index !== '' ? ' ' . $db_index : ''); diff --git a/.vortex/tooling/src/download-db-acquia b/.vortex/tooling/src/download-db-acquia index c012579da..182208433 100644 --- a/.vortex/tooling/src/download-db-acquia +++ b/.vortex/tooling/src/download-db-acquia @@ -21,35 +21,38 @@ execute_override(basename(__FILE__)); // ----------------------------------------------------------------------------- +// Database index suffix. +$db_index = getenv_default('VORTEX_DB_INDEX', ''); + // Acquia Cloud API key. -$acquia_key = getenv_required('VORTEX_DOWNLOAD_DB_ACQUIA_KEY', 'VORTEX_ACQUIA_KEY'); +$acquia_key = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_ACQUIA_KEY', 'VORTEX_DOWNLOAD_DB_ACQUIA_KEY', 'VORTEX_ACQUIA_KEY']))); // Acquia Cloud API secret. -$acquia_secret = getenv_required('VORTEX_DOWNLOAD_DB_ACQUIA_SECRET', 'VORTEX_ACQUIA_SECRET'); +$acquia_secret = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_ACQUIA_SECRET', 'VORTEX_DOWNLOAD_DB_ACQUIA_SECRET', 'VORTEX_ACQUIA_SECRET']))); // Application name. Used to discover UUID. -$app_name = getenv_required('VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME', 'VORTEX_ACQUIA_APP_NAME'); +$app_name = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_ACQUIA_APP_NAME', 'VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME', 'VORTEX_ACQUIA_APP_NAME']))); // Source environment name used to download the database dump from. -$environment = getenv_required('VORTEX_DOWNLOAD_DB_ENVIRONMENT'); +$environment = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_ENVIRONMENT', 'VORTEX_DOWNLOAD_DB_ENVIRONMENT']))); // Database name within source environment. -$db_name = getenv_required('VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME'); +$db_name = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_ACQUIA_DB_NAME', 'VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME']))); // Directory where DB dumps are stored. -$db_dir = getenv_default('VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data'); +$db_dir = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_ACQUIA_DB_DIR', 'VORTEX_DOWNLOAD_DB' . $db_index . '_DIR', 'VORTEX_DB' . $db_index . '_DIR', 'VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data']))); // Database dump file name. -$db_file = getenv_default('VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE', 'VORTEX_DOWNLOAD_DB_FILE', 'VORTEX_DB_FILE', 'db.sql'); +$db_file = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_ACQUIA_DB_FILE', 'VORTEX_DOWNLOAD_DB' . $db_index . '_FILE', 'VORTEX_DB' . $db_index . '_FILE', 'VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE', 'VORTEX_DOWNLOAD_DB_FILE', 'VORTEX_DB_FILE', 'db.sql']))); // Flag to download a fresh copy by triggering a new backup. -$fresh = getenv_default('VORTEX_DOWNLOAD_DB_FRESH', ''); +$fresh = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_FRESH', 'VORTEX_DOWNLOAD_DB_FRESH', '']))); // Interval in seconds to wait between backup status checks. -$backup_wait_interval = (int) getenv_default('VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_WAIT_INTERVAL', '10'); +$backup_wait_interval = (int) getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_ACQUIA_BACKUP_WAIT_INTERVAL', 'VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_WAIT_INTERVAL', '10']))); // Maximum time in seconds to wait for backup completion. -$backup_max_wait = (int) getenv_default('VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_MAX_WAIT', '600'); +$backup_max_wait = (int) getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_ACQUIA_BACKUP_MAX_WAIT', 'VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_MAX_WAIT', '600']))); // ----------------------------------------------------------------------------- diff --git a/.vortex/tooling/src/download-db-container-registry b/.vortex/tooling/src/download-db-container-registry index 53872d247..17a6508b0 100644 --- a/.vortex/tooling/src/download-db-container-registry +++ b/.vortex/tooling/src/download-db-container-registry @@ -17,25 +17,28 @@ execute_override(basename(__FILE__)); // ----------------------------------------------------------------------------- +// Database index suffix. +$db_index = getenv_default('VORTEX_DB_INDEX', ''); + // The container image containing database in a form of `/`. -$image = getenv_required('VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE', 'VORTEX_DB_IMAGE'); +$image = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_CONTAINER_REGISTRY_IMAGE', 'VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE', 'VORTEX_DB' . $db_index . '_IMAGE', 'VORTEX_DB_IMAGE']))); // Container registry name. // // Provide port, if required as `:`. -$registry = getenv_default('VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY', 'VORTEX_CONTAINER_REGISTRY', 'docker.io'); +$registry = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_CONTAINER_REGISTRY', 'VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY', 'VORTEX_CONTAINER_REGISTRY', 'docker.io']))); // The username to login into the container registry. -$registry_user = getenv_required('VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_USER', 'VORTEX_CONTAINER_REGISTRY_USER'); +$registry_user = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_CONTAINER_REGISTRY_USER', 'VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_USER', 'VORTEX_CONTAINER_REGISTRY_USER']))); // The password to login into the container registry. -$registry_pass = getenv_required('VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS', 'VORTEX_CONTAINER_REGISTRY_PASS'); +$registry_pass = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_CONTAINER_REGISTRY_PASS', 'VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS', 'VORTEX_CONTAINER_REGISTRY_PASS']))); // Directory with database dump file. -$db_dir = getenv_default('VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_DB_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data'); +$db_dir = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_CONTAINER_REGISTRY_DB_DIR', 'VORTEX_DOWNLOAD_DB' . $db_index . '_DIR', 'VORTEX_DB' . $db_index . '_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. -$image_base = getenv_default('VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE', 'VORTEX_DB_IMAGE_BASE', ''); +$image_base = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_CONTAINER_REGISTRY_IMAGE_BASE', 'VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE', 'VORTEX_DB' . $db_index . '_IMAGE_BASE', 'VORTEX_DB_IMAGE_BASE', '']))); // ----------------------------------------------------------------------------- diff --git a/.vortex/tooling/src/download-db-ftp b/.vortex/tooling/src/download-db-ftp index f6b9fe87b..30d9999bc 100755 --- a/.vortex/tooling/src/download-db-ftp +++ b/.vortex/tooling/src/download-db-ftp @@ -17,26 +17,29 @@ execute_override(basename(__FILE__)); // ----------------------------------------------------------------------------- +// Database index suffix. +$db_index = getenv_default('VORTEX_DB_INDEX', ''); + // The FTP user. -$ftp_user = getenv_required('VORTEX_DOWNLOAD_DB_FTP_USER'); +$ftp_user = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_FTP_USER', 'VORTEX_DOWNLOAD_DB_FTP_USER']))); // The FTP password. -$ftp_pass = getenv_required('VORTEX_DOWNLOAD_DB_FTP_PASS'); +$ftp_pass = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_FTP_PASS', 'VORTEX_DOWNLOAD_DB_FTP_PASS']))); // The FTP host. -$ftp_host = getenv_required('VORTEX_DOWNLOAD_DB_FTP_HOST'); +$ftp_host = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_FTP_HOST', 'VORTEX_DOWNLOAD_DB_FTP_HOST']))); // The FTP port. -$ftp_port = getenv_required('VORTEX_DOWNLOAD_DB_FTP_PORT'); +$ftp_port = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_FTP_PORT', 'VORTEX_DOWNLOAD_DB_FTP_PORT']))); // The file name, including any directories. -$ftp_file = getenv_required('VORTEX_DOWNLOAD_DB_FTP_FILE'); +$ftp_file = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_FTP_FILE', 'VORTEX_DOWNLOAD_DB_FTP_FILE']))); // Directory with database dump file. -$db_dir = getenv_default('VORTEX_DOWNLOAD_DB_FTP_DB_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data'); +$db_dir = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_FTP_DB_DIR', 'VORTEX_DOWNLOAD_DB' . $db_index . '_DIR', 'VORTEX_DB' . $db_index . '_DIR', 'VORTEX_DOWNLOAD_DB_FTP_DB_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data']))); // Database dump file name. -$db_file = getenv_default('VORTEX_DOWNLOAD_DB_FTP_DB_FILE', 'VORTEX_DOWNLOAD_DB_FILE', 'VORTEX_DB_FILE', 'db.sql'); +$db_file = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_FTP_DB_FILE', 'VORTEX_DOWNLOAD_DB' . $db_index . '_FILE', 'VORTEX_DB' . $db_index . '_FILE', 'VORTEX_DOWNLOAD_DB_FTP_DB_FILE', 'VORTEX_DOWNLOAD_DB_FILE', 'VORTEX_DB_FILE', 'db.sql']))); // ----------------------------------------------------------------------------- diff --git a/.vortex/tooling/src/download-db-lagoon b/.vortex/tooling/src/download-db-lagoon index 9acfeb8d1..925fc592b 100644 --- a/.vortex/tooling/src/download-db-lagoon +++ b/.vortex/tooling/src/download-db-lagoon @@ -26,41 +26,47 @@ execute_override(basename(__FILE__)); // ----------------------------------------------------------------------------- +// Database index suffix. +$db_index = getenv_default('VORTEX_DB_INDEX', ''); + // Flag to download a fresh copy of the database. -$fresh = getenv_default('VORTEX_DOWNLOAD_DB_FRESH', ''); +$fresh = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_FRESH', 'VORTEX_DOWNLOAD_DB_FRESH', '']))); // Lagoon project name. -$lagoon_project = getenv_required('VORTEX_DOWNLOAD_DB_LAGOON_PROJECT', 'LAGOON_PROJECT'); +$lagoon_project = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_LAGOON_PROJECT', 'VORTEX_DOWNLOAD_DB_LAGOON_PROJECT', 'LAGOON_PROJECT']))); // The source environment branch for the database source. -$environment = getenv_default('VORTEX_DOWNLOAD_DB_ENVIRONMENT', 'main'); +$environment = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_ENVIRONMENT', 'VORTEX_DOWNLOAD_DB_ENVIRONMENT', 'main']))); // Remote DB dump directory location. $remote_dir = '/tmp'; // Remote DB dump file name. Cached by the date suffix. -$remote_file = getenv_default('VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE', 'db_' . date('Ymd') . '.sql'); +$remote_file = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_LAGOON_REMOTE_FILE', 'VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE', 'db_' . date('Ymd') . '.sql']))); // Wildcard file name to cleanup previously created dump files. -$remote_file_cleanup = getenv_default('VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE_CLEANUP', 'db_*.sql'); +$remote_file_cleanup = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_LAGOON_REMOTE_FILE_CLEANUP', 'VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE_CLEANUP', 'db_*.sql']))); + +// SSH key fingerprint used to connect to a remote. +$ssh_fingerprint = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_SSH_FINGERPRINT', 'VORTEX_DOWNLOAD_DB_SSH_FINGERPRINT', '']))); // Default SSH file used if custom fingerprint is not provided. -$ssh_file = getenv_default('VORTEX_DOWNLOAD_DB_SSH_FILE', getenv('HOME') . '/.ssh/id_rsa'); +$ssh_file = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_SSH_FILE', 'VORTEX_DOWNLOAD_DB_SSH_FILE', getenv('HOME') . '/.ssh/id_rsa']))); // The SSH host of the Lagoon environment. -$ssh_host = getenv_default('VORTEX_DOWNLOAD_DB_LAGOON_SSH_HOST', 'ssh.lagoon.amazeeio.cloud'); +$ssh_host = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_LAGOON_SSH_HOST', 'VORTEX_DOWNLOAD_DB_LAGOON_SSH_HOST', 'ssh.lagoon.amazeeio.cloud']))); // The SSH port of the Lagoon environment. -$ssh_port = getenv_default('VORTEX_DOWNLOAD_DB_LAGOON_SSH_PORT', '32222'); +$ssh_port = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_LAGOON_SSH_PORT', 'VORTEX_DOWNLOAD_DB_LAGOON_SSH_PORT', '32222']))); // The SSH user of the Lagoon environment. -$ssh_user = getenv_default('VORTEX_DOWNLOAD_DB_LAGOON_SSH_USER', $lagoon_project . '-' . $environment); +$ssh_user = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_LAGOON_SSH_USER', 'VORTEX_DOWNLOAD_DB_LAGOON_SSH_USER', $lagoon_project . '-' . $environment]))); // Directory where DB dumps are stored on the host. -$db_dir = getenv_default('VORTEX_DOWNLOAD_DB_LAGOON_DB_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data'); +$db_dir = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_LAGOON_DB_DIR', 'VORTEX_DOWNLOAD_DB' . $db_index . '_DIR', 'VORTEX_DB' . $db_index . '_DIR', 'VORTEX_DOWNLOAD_DB_LAGOON_DB_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data']))); // Database dump file name on the host. -$db_file = getenv_default('VORTEX_DOWNLOAD_DB_LAGOON_DB_FILE', 'VORTEX_DOWNLOAD_DB_FILE', 'VORTEX_DB_FILE', 'db.sql'); +$db_file = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_LAGOON_DB_FILE', 'VORTEX_DOWNLOAD_DB' . $db_index . '_FILE', 'VORTEX_DB' . $db_index . '_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 = getenv_default('WEBROOT', 'web'); diff --git a/.vortex/tooling/src/download-db-s3 b/.vortex/tooling/src/download-db-s3 index 4644fabdf..aa2ee8a5e 100644 --- a/.vortex/tooling/src/download-db-s3 +++ b/.vortex/tooling/src/download-db-s3 @@ -19,26 +19,29 @@ execute_override(basename(__FILE__)); // ----------------------------------------------------------------------------- +// Database index suffix. +$db_index = getenv_default('VORTEX_DB_INDEX', ''); + // AWS access key. -$access_key = getenv_required('VORTEX_DOWNLOAD_DB_S3_ACCESS_KEY', 'S3_ACCESS_KEY'); +$access_key = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_S3_ACCESS_KEY', 'VORTEX_DOWNLOAD_DB_S3_ACCESS_KEY', 'S3_ACCESS_KEY']))); // AWS secret key. -$secret_key = getenv_required('VORTEX_DOWNLOAD_DB_S3_SECRET_KEY', 'S3_SECRET_KEY'); +$secret_key = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_S3_SECRET_KEY', 'VORTEX_DOWNLOAD_DB_S3_SECRET_KEY', 'S3_SECRET_KEY']))); // S3 bucket name. -$bucket = getenv_required('VORTEX_DOWNLOAD_DB_S3_BUCKET', 'S3_BUCKET'); +$bucket = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_S3_BUCKET', 'VORTEX_DOWNLOAD_DB_S3_BUCKET', 'S3_BUCKET']))); // S3 region. -$region = getenv_required('VORTEX_DOWNLOAD_DB_S3_REGION', 'S3_REGION'); +$region = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_S3_REGION', 'VORTEX_DOWNLOAD_DB_S3_REGION', 'S3_REGION']))); // S3 prefix (path within the bucket). -$prefix = getenv_default('VORTEX_DOWNLOAD_DB_S3_PREFIX', 'S3_PREFIX', ''); +$prefix = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_S3_PREFIX', 'VORTEX_DOWNLOAD_DB_S3_PREFIX', 'S3_PREFIX', '']))); // Directory with database dump file. -$db_dir = getenv_default('VORTEX_DOWNLOAD_DB_S3_DB_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data'); +$db_dir = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_S3_DB_DIR', 'VORTEX_DOWNLOAD_DB' . $db_index . '_DIR', 'VORTEX_DB' . $db_index . '_DIR', 'VORTEX_DOWNLOAD_DB_S3_DB_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data']))); // Database dump file name. -$db_file = getenv_default('VORTEX_DOWNLOAD_DB_S3_DB_FILE', 'VORTEX_DOWNLOAD_DB_FILE', 'VORTEX_DB_FILE', 'db.sql'); +$db_file = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_S3_DB_FILE', 'VORTEX_DOWNLOAD_DB' . $db_index . '_FILE', 'VORTEX_DB' . $db_index . '_FILE', 'VORTEX_DOWNLOAD_DB_S3_DB_FILE', 'VORTEX_DOWNLOAD_DB_FILE', 'VORTEX_DB_FILE', 'db.sql']))); // ----------------------------------------------------------------------------- diff --git a/.vortex/tooling/src/download-db-url b/.vortex/tooling/src/download-db-url index df8e7e985..c61fd65e5 100755 --- a/.vortex/tooling/src/download-db-url +++ b/.vortex/tooling/src/download-db-url @@ -17,17 +17,20 @@ execute_override(basename(__FILE__)); // ----------------------------------------------------------------------------- +// Database index suffix. +$db_index = getenv_default('VORTEX_DB_INDEX', ''); + // URL of the remote database. -$url = getenv_required('VORTEX_DOWNLOAD_DB_URL'); +$url = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_URL', 'VORTEX_DOWNLOAD_DB_URL']))); // Directory with database dump file. -$db_dir = getenv_default('VORTEX_DOWNLOAD_DB_URL_DB_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data'); +$db_dir = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_URL_DB_DIR', 'VORTEX_DOWNLOAD_DB' . $db_index . '_DIR', 'VORTEX_DB' . $db_index . '_DIR', 'VORTEX_DOWNLOAD_DB_URL_DB_DIR', 'VORTEX_DOWNLOAD_DB_DIR', 'VORTEX_DB_DIR', './.data']))); // Database dump file name. -$db_file = getenv_default('VORTEX_DOWNLOAD_DB_URL_DB_FILE', 'VORTEX_DOWNLOAD_DB_FILE', 'VORTEX_DB_FILE', 'db.sql'); +$db_file = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_URL_DB_FILE', 'VORTEX_DOWNLOAD_DB' . $db_index . '_FILE', 'VORTEX_DB' . $db_index . '_FILE', 'VORTEX_DOWNLOAD_DB_URL_DB_FILE', 'VORTEX_DOWNLOAD_DB_FILE', 'VORTEX_DB_FILE', 'db.sql']))); // Password for unzipping password-protected zip files. -$unzip_password = getenv_default('VORTEX_DOWNLOAD_DB_UNZIP_PASSWORD', ''); +$unzip_password = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_UNZIP_PASSWORD', 'VORTEX_DOWNLOAD_DB_UNZIP_PASSWORD', '']))); // ----------------------------------------------------------------------------- diff --git a/.vortex/tooling/src/notify b/.vortex/tooling/src/notify index 83fe88d1f..0ea93135b 100755 --- a/.vortex/tooling/src/notify +++ b/.vortex/tooling/src/notify @@ -67,7 +67,7 @@ if (empty($channels)) { quit(0); } -if (!in_array($event, ['pre_deployment', 'post_deployment'])) { +if (!in_array($event, ['pre_deployment', 'post_deployment'], TRUE)) { fail('Unsupported event %s provided.', $event); } diff --git a/.vortex/tooling/src/provision b/.vortex/tooling/src/provision index 4a53a499f..91f2109c6 100755 --- a/.vortex/tooling/src/provision +++ b/.vortex/tooling/src/provision @@ -53,6 +53,9 @@ $provision_post_operations_skip = getenv_default('VORTEX_PROVISION_POST_OPERATIO // drush config:import from silently overwriting those changes. $provision_verify_config = getenv_default('VORTEX_PROVISION_VERIFY_CONFIG_UNCHANGED_AFTER_UPDATE', '0'); +// Skip cache rebuild after database updates. +$provision_cache_rebuild_after_db_update_skip = getenv_default('VORTEX_PROVISION_CACHE_REBUILD_AFTER_DB_UPDATE_SKIP', '0'); + // Provision database dump file. // // If not set, it will be auto-discovered from the VORTEX_PROVISION_DB_DIR @@ -150,8 +153,10 @@ function provision_from_db(string $provision_db): void { * The admin email (optional). * @param bool $has_config_files * Whether config files exist. + * @param bool $is_fallback + * Whether this is a fallback installation (no --existing-config). */ -function provision_from_profile(string $profile, string $site_name, string $site_email, string $admin_email, bool $has_config_files): void { +function provision_from_profile(string $profile, string $site_name, string $site_email, string $admin_email, bool $has_config_files, bool $is_fallback = FALSE): void { $opts = [ escapeshellarg($profile), '--site-name=' . escapeshellarg($site_name), @@ -165,7 +170,7 @@ function provision_from_profile(string $profile, string $site_name, string $site $opts[] = '--account-mail=' . escapeshellarg($admin_email); } - if ($has_config_files) { + if (!$is_fallback && $has_config_files) { $opts[] = '--existing-config'; } @@ -174,6 +179,13 @@ function provision_from_profile(string $profile, string $site_name, string $site drush('site:install ' . implode(' ', $opts)); + // On fallback, enable Shield to protect the site and skip post-provision + // operations since the site was installed from profile without configuration. + if ($is_fallback) { + drush('pm:install shield'); + putenv('VORTEX_PROVISION_POST_OPERATIONS_SKIP=1'); + } + pass('Installed a site from the profile.'); } @@ -369,7 +381,7 @@ if ($provision_type === 'database') { note('Existing site content will be removed and fresh content will be imported from the database dump file.'); if (!file_exists($provision_db) && $provision_fallback_to_profile === '1') { info('Database dump file is not available. Falling back to profile installation.'); - provision_from_profile($drupal_profile, $drupal_site_name, $drupal_site_email, $drupal_admin_email, $site_has_config_files); + provision_from_profile($drupal_profile, $drupal_site_name, $drupal_site_email, $drupal_admin_email, $site_has_config_files, TRUE); } else { provision_from_db($provision_db); @@ -389,7 +401,7 @@ if ($provision_type === 'database') { note('Database is baked into the container image.'); if ($provision_fallback_to_profile === '1') { info('Database in the container image is not available. Falling back to profile installation.'); - provision_from_profile($drupal_profile, $drupal_site_name, $drupal_site_email, $drupal_admin_email, $site_has_config_files); + provision_from_profile($drupal_profile, $drupal_site_name, $drupal_site_email, $drupal_admin_email, $site_has_config_files, TRUE); $provision_override_db = '1'; putenv('VORTEX_PROVISION_OVERRIDE_DB=1'); } @@ -403,7 +415,7 @@ if ($provision_type === 'database') { note('Fresh site content will be imported from the database dump file.'); if (!file_exists($provision_db) && $provision_fallback_to_profile === '1') { info('Database dump file is not available. Falling back to profile installation.'); - provision_from_profile($drupal_profile, $drupal_site_name, $drupal_site_email, $drupal_admin_email, $site_has_config_files); + provision_from_profile($drupal_profile, $drupal_site_name, $drupal_site_email, $drupal_admin_email, $site_has_config_files, TRUE); } else { provision_from_db($provision_db); @@ -450,6 +462,9 @@ $environment = trim(drush("php:eval \"print \\Drupal\\core\\Site\\Settings::get( info('Current Drupal environment: %s', $environment); echo PHP_EOL; +// Re-read in case provision_from_profile() changed it via putenv(). +$provision_post_operations_skip = getenv('VORTEX_PROVISION_POST_OPERATIONS_SKIP') ?: $provision_post_operations_skip; + if ($provision_post_operations_skip === '1') { info('Skipped running of post-provision operations as VORTEX_PROVISION_POST_OPERATIONS_SKIP is set to 1.'); echo PHP_EOL; @@ -515,6 +530,17 @@ else { pass('Completed running database updates.'); echo PHP_EOL; +if ($provision_cache_rebuild_after_db_update_skip !== '1') { + task('Clearing cache after database updates.'); + drush('cache:rebuild'); + pass('Cache was cleared.'); + echo PHP_EOL; +} +else { + pass('Skipped cache rebuild after database updates.'); + echo PHP_EOL; +} + // Import configuration if config files are present. if ($site_has_config_files) { task('Importing configuration.'); diff --git a/.vortex/tooling/tests/Unit/DeployArtifactTest.php b/.vortex/tooling/tests/Unit/DeployArtifactTest.php index 1f36a4d17..ce9b8557c 100644 --- a/.vortex/tooling/tests/Unit/DeployArtifactTest.php +++ b/.vortex/tooling/tests/Unit/DeployArtifactTest.php @@ -22,6 +22,7 @@ protected function setUp(): void { 'VORTEX_DEPLOY_ARTIFACT_SRC' => self::$tmp . '/src', 'VORTEX_DEPLOY_ARTIFACT_ROOT' => self::$tmp . '/root', 'HOME' => self::$tmp, + 'TMPDIR' => self::$tmp, ]); // Create required directories. @@ -29,6 +30,10 @@ protected function setUp(): void { mkdir(self::$tmp . '/root', 0755, TRUE); mkdir(self::$tmp . '/root/.git', 0755, TRUE); file_put_contents(self::$tmp . '/root/.gitignore.artifact', "vendor/\n"); + + // Pre-create the dummy git-artifact binary so hash_file() works when the + // request() mock returns without actually writing the file. + $this->createDummyGitArtifactBinary(); } public function testMissingGitRemote(): void { @@ -63,17 +68,13 @@ public function testDefaultValues(): void { 'result_code' => 0, ]); - // Mock composer install. - $this->mockPassthru([ - 'cmd' => 'composer global require --dev -n --ansi --prefer-source --ignore-platform-reqs drevops/git-artifact:~1.2', - 'output' => 'Installing git-artifact', - 'result_code' => 0, - ]); + // Mock git-artifact download. + $this->mockGitArtifactDownload(); // Mock git-artifact command. $git_artifact_cmd = sprintf( '%s %s --root=%s --src=%s --branch=%s --gitignore=%s --log=%s -vvv', - escapeshellarg(self::$tmp . '/.composer/vendor/bin/git-artifact'), + escapeshellarg($this->getGitArtifactBinPath()), escapeshellarg('git@github.com:org/repo.git'), escapeshellarg(self::$tmp . '/root'), escapeshellarg(self::$tmp . '/src'), @@ -124,17 +125,13 @@ public function testConfigureGitUser(): void { 'result_code' => 0, ]); - // Mock composer install. - $this->mockPassthru([ - 'cmd' => 'composer global require --dev -n --ansi --prefer-source --ignore-platform-reqs drevops/git-artifact:~1.2', - 'output' => 'Installing git-artifact', - 'result_code' => 0, - ]); + // Mock git-artifact download. + $this->mockGitArtifactDownload(); // Mock git-artifact command. $git_artifact_cmd = sprintf( '%s %s --root=%s --src=%s --branch=%s --gitignore=%s --log=%s -vvv', - escapeshellarg(self::$tmp . '/.composer/vendor/bin/git-artifact'), + escapeshellarg($this->getGitArtifactBinPath()), escapeshellarg('git@github.com:org/repo.git'), escapeshellarg(self::$tmp . '/root'), escapeshellarg(self::$tmp . '/src'), @@ -226,7 +223,7 @@ public function testSshSetupFailure(): void { $this->runScript('src/deploy-artifact'); } - public function testComposerInstallFailure(): void { + public function testGitArtifactDownloadFailure(): void { // Mock shell_exec for git config checks. $this->mockShellExecMultiple([ ['value' => 'Existing User'], @@ -240,19 +237,36 @@ public function testComposerInstallFailure(): void { 'result_code' => 0, ]); - // Mock composer install failure. - $this->mockPassthru([ - 'cmd' => 'composer global require --dev -n --ansi --prefer-source --ignore-platform-reqs drevops/git-artifact:~1.2', - 'output' => 'Composer install failed', - 'result_code' => 1, + // Mock git-artifact download failure. + $this->mockRequest( + 'https://github.com/drevops/git-artifact/releases/download/1.4.0/git-artifact', + ['method' => 'GET'], + ['ok' => FALSE, 'status' => 404, 'body' => 'Not Found'], + ); + + $this->runScriptError('src/deploy-artifact', 'Failed to download git-artifact binary.'); + } + + public function testGitArtifactSha256Failure(): void { + // Override with a wrong SHA to trigger verification failure. + $this->envSet('VORTEX_DEPLOY_ARTIFACT_GIT_ARTIFACT_SHA256', 'wrong-sha256-value'); + + // Mock shell_exec for git config checks. + $this->mockShellExecMultiple([ + ['value' => 'Existing User'], + ['value' => 'existing@example.com'], ]); - $this->mockQuit(1); + // Mock setup-ssh. + $this->mockPassthru([ + 'cmd' => $this->getSetupSshPath(), + 'output' => 'SSH setup complete', + 'result_code' => 0, + ]); - $this->expectException(QuitErrorException::class); - $this->expectExceptionCode(1); + $this->mockGitArtifactDownload(); - $this->runScript('src/deploy-artifact'); + $this->runScriptError('src/deploy-artifact', 'SHA256 checksum verification failed for git-artifact binary.'); } public function testGitArtifactFailure(): void { @@ -269,17 +283,13 @@ public function testGitArtifactFailure(): void { 'result_code' => 0, ]); - // Mock composer install. - $this->mockPassthru([ - 'cmd' => 'composer global require --dev -n --ansi --prefer-source --ignore-platform-reqs drevops/git-artifact:~1.2', - 'output' => 'Installing git-artifact', - 'result_code' => 0, - ]); + // Mock git-artifact download. + $this->mockGitArtifactDownload(); // Mock git-artifact command failure. $git_artifact_cmd = sprintf( '%s %s --root=%s --src=%s --branch=%s --gitignore=%s --log=%s -vvv', - escapeshellarg(self::$tmp . '/.composer/vendor/bin/git-artifact'), + escapeshellarg($this->getGitArtifactBinPath()), escapeshellarg('git@github.com:org/repo.git'), escapeshellarg(self::$tmp . '/root'), escapeshellarg(self::$tmp . '/src'), @@ -320,12 +330,8 @@ public function testRealpathFailure(): void { 'result_code' => 0, ]); - // Mock composer install. - $this->mockPassthru([ - 'cmd' => 'composer global require --dev -n --ansi --prefer-source --ignore-platform-reqs drevops/git-artifact:~1.2', - 'output' => 'Installing git-artifact', - 'result_code' => 0, - ]); + // Mock git-artifact download. + $this->mockGitArtifactDownload(); $this->runScriptError('src/deploy-artifact', 'Failed to resolve real path for deployment directories.'); } @@ -346,17 +352,13 @@ public function testCustomBranch(): void { 'result_code' => 0, ]); - // Mock composer install. - $this->mockPassthru([ - 'cmd' => 'composer global require --dev -n --ansi --prefer-source --ignore-platform-reqs drevops/git-artifact:~1.2', - 'output' => 'Installing git-artifact', - 'result_code' => 0, - ]); + // Mock git-artifact download. + $this->mockGitArtifactDownload(); // Mock git-artifact command with custom branch. $git_artifact_cmd = sprintf( '%s %s --root=%s --src=%s --branch=%s --gitignore=%s --log=%s -vvv', - escapeshellarg(self::$tmp . '/.composer/vendor/bin/git-artifact'), + escapeshellarg($this->getGitArtifactBinPath()), escapeshellarg('git@github.com:org/repo.git'), escapeshellarg(self::$tmp . '/root'), escapeshellarg(self::$tmp . '/src'), @@ -380,4 +382,31 @@ protected function getSetupSshPath(): string { return dirname(__DIR__, 2) . '/src/setup-ssh'; } + protected function getGitArtifactBinPath(): string { + return self::$tmp . '/git-artifact'; + } + + /** + * Create a dummy git-artifact binary and set the matching SHA256. + * + * The mock request() with save_to will truncate the file to empty via + * fopen('w'), so we set the SHA to match an empty file. + */ + protected function createDummyGitArtifactBinary(): void { + $bin_path = $this->getGitArtifactBinPath(); + file_put_contents($bin_path, ''); + $this->envSet('VORTEX_DEPLOY_ARTIFACT_GIT_ARTIFACT_SHA256', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'); + } + + /** + * Mock the git-artifact download request. + */ + protected function mockGitArtifactDownload(): void { + $this->mockRequest( + 'https://github.com/drevops/git-artifact/releases/download/1.4.0/git-artifact', + ['method' => 'GET'], + ['ok' => TRUE, 'status' => 200, 'body' => ''], + ); + } + } diff --git a/.vortex/tooling/tests/Unit/ProvisionTest.php b/.vortex/tooling/tests/Unit/ProvisionTest.php index 11ba16eaf..5474e9d9e 100644 --- a/.vortex/tooling/tests/Unit/ProvisionTest.php +++ b/.vortex/tooling/tests/Unit/ProvisionTest.php @@ -179,12 +179,18 @@ public function testDatabaseProvisionNoSiteNoDumpFallbackToProfile(): void { 'result_code' => 0, ]); - // Drush site:install. + // Drush site:install (without --existing-config since this is a fallback). $this->mockPassthru([ 'cmd' => $this->drushCmd("site:install 'standard' --site-name='Test Site' --site-mail='test@example.com' --account-name=admin install_configure_form.enable_update_status_module=NULL install_configure_form.enable_update_status_emails=NULL"), 'result_code' => 0, ]); + // Drush pm:install shield (fallback enables Shield). + $this->mockPassthru([ + 'cmd' => $this->drushCmd('pm:install shield'), + 'result_code' => 0, + ]); + // Drush php:eval (environment). $this->mockPassthru([ 'cmd' => $this->drushCmd("php:eval \"print \\Drupal\\core\\Site\\Settings::get('environment');\""), @@ -192,6 +198,8 @@ public function testDatabaseProvisionNoSiteNoDumpFallbackToProfile(): void { 'result_code' => 0, ]); + // Fallback sets VORTEX_PROVISION_POST_OPERATIONS_SKIP=1, so script exits + // early after environment detection. $this->mockQuit(0); $this->expectException(QuitSuccessException::class); @@ -215,12 +223,18 @@ public function testDatabaseProvisionNoSiteDbImageFallbackToProfile(): void { 'result_code' => 0, ]); - // Drush site:install. + // Drush site:install (without --existing-config since this is a fallback). $this->mockPassthru([ 'cmd' => $this->drushCmd("site:install 'standard' --site-name='Test Site' --site-mail='test@example.com' --account-name=admin install_configure_form.enable_update_status_module=NULL install_configure_form.enable_update_status_emails=NULL"), 'result_code' => 0, ]); + // Drush pm:install shield (fallback enables Shield). + $this->mockPassthru([ + 'cmd' => $this->drushCmd('pm:install shield'), + 'result_code' => 0, + ]); + // Drush php:eval (environment). $this->mockPassthru([ 'cmd' => $this->drushCmd("php:eval \"print \\Drupal\\core\\Site\\Settings::get('environment');\""), @@ -393,7 +407,13 @@ public function testPostOperationsWithMaintenanceMode(): void { 'result_code' => 0, ]); - // Drush cache:rebuild. + // Drush cache:rebuild (after database updates). + $this->mockPassthru([ + 'cmd' => $this->drushCmd('cache:rebuild'), + 'result_code' => 0, + ]); + + // Drush cache:rebuild (post-provision). $this->mockPassthru([ 'cmd' => $this->drushCmd('cache:rebuild'), 'result_code' => 0, @@ -418,7 +438,7 @@ public function testPostOperationsWithMaintenanceMode(): void { $this->assertStringContainsString('Enabling maintenance mode.', $output); $this->assertStringContainsString('Completed running database updates.', $output); - $this->assertStringContainsString('Cache was rebuilt.', $output); + $this->assertStringContainsString('Cache was cleared.', $output); $this->assertStringContainsString('Completed deployment hooks.', $output); $this->assertStringContainsString('Disabling maintenance mode.', $output); } @@ -450,6 +470,12 @@ public function testPostOperationsWithConfigImport(): void { 'result_code' => 0, ]); + // Drush cache:rebuild (after database updates). + $this->mockPassthru([ + 'cmd' => $this->drushCmd('cache:rebuild'), + 'result_code' => 0, + ]); + // Drush config:import. $this->mockPassthru([ 'cmd' => $this->drushCmd('config:import'), @@ -469,7 +495,7 @@ public function testPostOperationsWithConfigImport(): void { 'result_code' => 0, ]); - // Drush cache:rebuild. + // Drush cache:rebuild (post-provision). $this->mockPassthru([ 'cmd' => $this->drushCmd('cache:rebuild'), 'result_code' => 0, @@ -488,6 +514,7 @@ public function testPostOperationsWithConfigImport(): void { $this->assertStringContainsString('Updated site UUID from the configuration', $output); $this->assertStringContainsString('Completed running database updates.', $output); + $this->assertStringContainsString('Cache was cleared.', $output); $this->assertStringContainsString('Completed configuration import.', $output); $this->assertStringContainsString('Completed config_split configuration import.', $output); $this->assertStringContainsString('Cache was rebuilt.', $output); @@ -696,7 +723,13 @@ public function testPostOperationsWithSanitizeDb(): void { 'result_code' => 0, ]); - // Drush cache:rebuild. + // Drush cache:rebuild (after database updates). + $this->mockPassthru([ + 'cmd' => $this->drushCmd('cache:rebuild'), + 'result_code' => 0, + ]); + + // Drush cache:rebuild (post-provision). $this->mockPassthru([ 'cmd' => $this->drushCmd('cache:rebuild'), 'result_code' => 0, @@ -787,7 +820,13 @@ public function testPostOperationsWithSanitizeDbFull(): void { 'result_code' => 0, ]); - // Drush cache:rebuild. + // Drush cache:rebuild (after database updates). + $this->mockPassthru([ + 'cmd' => $this->drushCmd('cache:rebuild'), + 'result_code' => 0, + ]); + + // Drush cache:rebuild (post-provision). $this->mockPassthru([ 'cmd' => $this->drushCmd('cache:rebuild'), 'result_code' => 0, @@ -872,7 +911,13 @@ public function testPostOperationsWithCustomScripts(): void { 'result_code' => 0, ]); - // Drush cache:rebuild. + // Drush cache:rebuild (after database updates). + $this->mockPassthru([ + 'cmd' => $this->drushCmd('cache:rebuild'), + 'result_code' => 0, + ]); + + // Drush cache:rebuild (post-provision). $this->mockPassthru([ 'cmd' => $this->drushCmd('cache:rebuild'), 'result_code' => 0, @@ -926,7 +971,13 @@ public function testPostOperationsWithCustomScriptFails(): void { 'result_code' => 0, ]); - // Drush cache:rebuild. + // Drush cache:rebuild (after database updates). + $this->mockPassthru([ + 'cmd' => $this->drushCmd('cache:rebuild'), + 'result_code' => 0, + ]); + + // Drush cache:rebuild (post-provision). $this->mockPassthru([ 'cmd' => $this->drushCmd('cache:rebuild'), 'result_code' => 0, @@ -973,7 +1024,13 @@ public function testPostOperationsWithCustomScriptsEmptyDir(): void { 'result_code' => 0, ]); - // Drush cache:rebuild. + // Drush cache:rebuild (after database updates). + $this->mockPassthru([ + 'cmd' => $this->drushCmd('cache:rebuild'), + 'result_code' => 0, + ]); + + // Drush cache:rebuild (post-provision). $this->mockPassthru([ 'cmd' => $this->drushCmd('cache:rebuild'), 'result_code' => 0, @@ -1050,6 +1107,12 @@ public function testPostOperationsWithVerifyConfig(): void { 'result_code' => 0, ]); + // Drush cache:rebuild (after database updates). + $this->mockPassthru([ + 'cmd' => $this->drushCmd('cache:rebuild'), + 'result_code' => 0, + ]); + // Drush config:import. $this->mockPassthru([ 'cmd' => $this->drushCmd('config:import'), @@ -1063,7 +1126,7 @@ public function testPostOperationsWithVerifyConfig(): void { 'result_code' => 0, ]); - // Drush cache:rebuild. + // Drush cache:rebuild (post-provision). $this->mockPassthru([ 'cmd' => $this->drushCmd('cache:rebuild'), 'result_code' => 0, From 9b47e3e8df9c6d706461df3e1501844ae3274ce6 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Tue, 24 Mar 2026 21:51:44 +1100 Subject: [PATCH 2/2] Fixed container registry image fallback ordering and used 'getenv_default()' for re-read. --- .vortex/tooling/src/download-db-container-registry | 4 ++-- .vortex/tooling/src/provision | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.vortex/tooling/src/download-db-container-registry b/.vortex/tooling/src/download-db-container-registry index 17a6508b0..708525695 100644 --- a/.vortex/tooling/src/download-db-container-registry +++ b/.vortex/tooling/src/download-db-container-registry @@ -21,7 +21,7 @@ execute_override(basename(__FILE__)); $db_index = getenv_default('VORTEX_DB_INDEX', ''); // The container image containing database in a form of `/`. -$image = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_CONTAINER_REGISTRY_IMAGE', 'VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE', 'VORTEX_DB' . $db_index . '_IMAGE', 'VORTEX_DB_IMAGE']))); +$image = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_CONTAINER_REGISTRY_IMAGE', 'VORTEX_DB' . $db_index . '_IMAGE', 'VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE', 'VORTEX_DB_IMAGE']))); // Container registry name. // @@ -38,7 +38,7 @@ $registry_pass = getenv_required(...array_values(array_unique(['VORTEX_DOWNLOAD_ $db_dir = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_CONTAINER_REGISTRY_DB_DIR', 'VORTEX_DOWNLOAD_DB' . $db_index . '_DIR', 'VORTEX_DB' . $db_index . '_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. -$image_base = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_CONTAINER_REGISTRY_IMAGE_BASE', 'VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE', 'VORTEX_DB' . $db_index . '_IMAGE_BASE', 'VORTEX_DB_IMAGE_BASE', '']))); +$image_base = getenv_default(...array_values(array_unique(['VORTEX_DOWNLOAD_DB' . $db_index . '_CONTAINER_REGISTRY_IMAGE_BASE', 'VORTEX_DB' . $db_index . '_IMAGE_BASE', 'VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE', 'VORTEX_DB_IMAGE_BASE', '']))); // ----------------------------------------------------------------------------- diff --git a/.vortex/tooling/src/provision b/.vortex/tooling/src/provision index 91f2109c6..f2cd60de6 100755 --- a/.vortex/tooling/src/provision +++ b/.vortex/tooling/src/provision @@ -463,7 +463,7 @@ info('Current Drupal environment: %s', $environment); echo PHP_EOL; // Re-read in case provision_from_profile() changed it via putenv(). -$provision_post_operations_skip = getenv('VORTEX_PROVISION_POST_OPERATIONS_SKIP') ?: $provision_post_operations_skip; +$provision_post_operations_skip = getenv_default('VORTEX_PROVISION_POST_OPERATIONS_SKIP', $provision_post_operations_skip); if ($provision_post_operations_skip === '1') { info('Skipped running of post-provision operations as VORTEX_PROVISION_POST_OPERATIONS_SKIP is set to 1.');