From fd6aac3081d22a489bdf9f2139912c1615ae1f64 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 9 Sep 2026 10:15:23 +1000 Subject: [PATCH 01/11] [#3113] Converged the 'extract_json_*' copies onto one quoted form. --- .vortex/tooling/src/vortex-fetch-db-acquia | 12 ++--- .vortex/tooling/src/vortex-notify-github | 23 +++++----- .vortex/tooling/src/vortex-notify-jira | 45 +++++++++---------- .../tooling/src/vortex-task-copy-db-acquia | 8 ++-- .../tooling/src/vortex-task-copy-files-acquia | 8 ++-- .../src/vortex-task-purge-cache-acquia | 8 ++-- 6 files changed, 49 insertions(+), 55 deletions(-) diff --git a/.vortex/tooling/src/vortex-fetch-db-acquia b/.vortex/tooling/src/vortex-fetch-db-acquia index 03730e331d..a401ab4d7d 100755 --- a/.vortex/tooling/src/vortex-fetch-db-acquia +++ b/.vortex/tooling/src/vortex-fetch-db-acquia @@ -83,20 +83,20 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on -for cmd in php curl gunzip; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done - -info "Started database dump fetch from Acquia." - extract_json_last_value() { - local key=${1} + local key="${1}" php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); \$last=array_pop(\$data); isset(\$last[\"${key}\"]) ? print trim(json_encode(\$last[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" } extract_json_value() { - local key=${1} + local key="${1}" php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); isset(\$data[\"${key}\"]) ? print trim(json_encode(\$data[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" } +for cmd in php curl gunzip; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done + +info "Started database dump fetch from Acquia." + [ -z "${VORTEX_FETCH_DB_ACQUIA_KEY}" ] && fail "Missing required value for VORTEX_FETCH_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY." [ -z "${VORTEX_FETCH_DB_ACQUIA_SECRET}" ] && fail "Missing required value for VORTEX_FETCH_DB_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." [ -z "${VORTEX_FETCH_DB_ACQUIA_APP_NAME}" ] && fail "Missing required value for VORTEX_FETCH_DB_ACQUIA_APP_NAME or VORTEX_ACQUIA_APP_NAME." diff --git a/.vortex/tooling/src/vortex-notify-github b/.vortex/tooling/src/vortex-notify-github index 237a3c83b1..e833855927 100755 --- a/.vortex/tooling/src/vortex-notify-github +++ b/.vortex/tooling/src/vortex-notify-github @@ -64,6 +64,16 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on +extract_json_first_value() { + local key="${1}" + php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); \$first=reset(\$data); isset(\$first[\"${key}\"]) ? print trim(json_encode(\$first[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" +} + +extract_json_value() { + local key="${1}" + php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); isset(\$data[\"${key}\"]) ? print trim(json_encode(\$data[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" +} + for cmd in php curl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done if [ -n "${VORTEX_NOTIFY_GITHUB_BRANCHES}" ]; then @@ -81,19 +91,6 @@ fi info "Started GitHub notification for ${VORTEX_NOTIFY_GITHUB_EVENT} event." -# -# Function to extract last value from JSON object passed via STDIN. -# -extract_json_first_value() { - local key=${1} - php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); \$first=reset(\$data); isset(\$first[\"${key}\"]) ? print trim(json_encode(\$first[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" -} - -extract_json_value() { - local key=${1} - php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); isset(\$data[\"${key}\"]) ? print trim(json_encode(\$data[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" -} - if [ "${VORTEX_NOTIFY_GITHUB_EVENT}" = "pre_deployment" ]; then info "GitHub pre-deployment notification summary:" note "Repository : ${VORTEX_NOTIFY_GITHUB_REPOSITORY}" diff --git a/.vortex/tooling/src/vortex-notify-jira b/.vortex/tooling/src/vortex-notify-jira index 05df2c0cd6..1c2340a16a 100755 --- a/.vortex/tooling/src/vortex-notify-jira +++ b/.vortex/tooling/src/vortex-notify-jira @@ -88,30 +88,6 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on -for cmd in php curl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done - -if [ -n "${VORTEX_NOTIFY_JIRA_BRANCHES}" ]; then - if ! echo ",${VORTEX_NOTIFY_JIRA_BRANCHES}," | grep -qF ",${VORTEX_NOTIFY_BRANCH-},"; then - pass "Skipped JIRA notification for branch \"${VORTEX_NOTIFY_BRANCH-}\"." - exit 0 - fi -fi - -[ -z "${VORTEX_NOTIFY_JIRA_USER_EMAIL}" ] && fail "Missing required value for VORTEX_NOTIFY_JIRA_USER_EMAIL." -[ -z "${VORTEX_NOTIFY_JIRA_TOKEN}" ] && fail "Missing required value for VORTEX_NOTIFY_JIRA_TOKEN." -[ -z "${VORTEX_NOTIFY_JIRA_LABEL}" ] && fail "Missing required value for VORTEX_NOTIFY_JIRA_LABEL." -[ -z "${VORTEX_NOTIFY_JIRA_PROJECT}" ] && fail "Missing required value for VORTEX_NOTIFY_JIRA_PROJECT." - -info "Started JIRA notification." - -if [ "${VORTEX_NOTIFY_JIRA_EVENT}" = "pre_deployment" ]; then - pass "Skipped JIRA notification for pre_deployment event." - exit 0 -fi - -# -# Function to extract last value from JSON object passed via STDIN. -# extract_json_first_value() { local key="${1}" php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); \$first=reset(\$data); isset(\$first[\"${key}\"]) ? print trim(json_encode(\$first[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" @@ -137,6 +113,27 @@ extract_issue() { echo "${1}" | sed -nE "s/([^\/]+\/)?([A-Za-z0-9]+\-[0-9]+).*/\2/p" } +for cmd in php curl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done + +if [ -n "${VORTEX_NOTIFY_JIRA_BRANCHES}" ]; then + if ! echo ",${VORTEX_NOTIFY_JIRA_BRANCHES}," | grep -qF ",${VORTEX_NOTIFY_BRANCH-},"; then + pass "Skipped JIRA notification for branch \"${VORTEX_NOTIFY_BRANCH-}\"." + exit 0 + fi +fi + +[ -z "${VORTEX_NOTIFY_JIRA_USER_EMAIL}" ] && fail "Missing required value for VORTEX_NOTIFY_JIRA_USER_EMAIL." +[ -z "${VORTEX_NOTIFY_JIRA_TOKEN}" ] && fail "Missing required value for VORTEX_NOTIFY_JIRA_TOKEN." +[ -z "${VORTEX_NOTIFY_JIRA_LABEL}" ] && fail "Missing required value for VORTEX_NOTIFY_JIRA_LABEL." +[ -z "${VORTEX_NOTIFY_JIRA_PROJECT}" ] && fail "Missing required value for VORTEX_NOTIFY_JIRA_PROJECT." + +info "Started JIRA notification." + +if [ "${VORTEX_NOTIFY_JIRA_EVENT}" = "pre_deployment" ]; then + pass "Skipped JIRA notification for pre_deployment event." + exit 0 +fi + task "Extracting issue." issue="$(extract_issue "${VORTEX_NOTIFY_JIRA_BRANCH}")" [ -z "${issue}" ] && pass "Branch ${VORTEX_NOTIFY_JIRA_BRANCH} does not contain issue number." && exit 0 diff --git a/.vortex/tooling/src/vortex-task-copy-db-acquia b/.vortex/tooling/src/vortex-task-copy-db-acquia index 49dc8a505c..ef506c24d9 100755 --- a/.vortex/tooling/src/vortex-task-copy-db-acquia +++ b/.vortex/tooling/src/vortex-task-copy-db-acquia @@ -53,18 +53,18 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on -info "Started database copying between environments in Acquia." - extract_json_last_value() { - local key=${1} + local key="${1}" php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); \$last=array_pop(\$data); isset(\$last[\"${key}\"]) ? print trim(json_encode(\$last[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" } extract_json_value() { - local key=${1} + local key="${1}" php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); isset(\$data[\"${key}\"]) ? print trim(json_encode(\$data[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" } +info "Started database copying between environments in Acquia." + for cmd in curl php; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done [ -z "${VORTEX_TASK_COPY_DB_ACQUIA_KEY}" ] && fail "Missing required value for VORTEX_TASK_COPY_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY." diff --git a/.vortex/tooling/src/vortex-task-copy-files-acquia b/.vortex/tooling/src/vortex-task-copy-files-acquia index 99e45f4026..6d856060ba 100755 --- a/.vortex/tooling/src/vortex-task-copy-files-acquia +++ b/.vortex/tooling/src/vortex-task-copy-files-acquia @@ -50,10 +50,6 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on -for cmd in php curl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done - -info "Started files copying between environments in Acquia." - extract_json_last_value() { local key="${1}" php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); \$last=array_pop(\$data); isset(\$last[\"${key}\"]) ? print trim(json_encode(\$last[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" @@ -64,6 +60,10 @@ extract_json_value() { php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); isset(\$data[\"${key}\"]) ? print trim(json_encode(\$data[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" } +for cmd in php curl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done + +info "Started files copying between environments in Acquia." + [ -z "${VORTEX_TASK_COPY_FILES_ACQUIA_KEY}" ] && fail "Missing required value for VORTEX_TASK_COPY_FILES_ACQUIA_KEY or VORTEX_ACQUIA_KEY." [ -z "${VORTEX_TASK_COPY_FILES_ACQUIA_SECRET}" ] && fail "Missing required value for VORTEX_TASK_COPY_FILES_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." [ -z "${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME}" ] && fail "Missing required value for VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME or VORTEX_ACQUIA_APP_NAME." diff --git a/.vortex/tooling/src/vortex-task-purge-cache-acquia b/.vortex/tooling/src/vortex-task-purge-cache-acquia index 6e7b3020b2..d719e34fb3 100755 --- a/.vortex/tooling/src/vortex-task-purge-cache-acquia +++ b/.vortex/tooling/src/vortex-task-purge-cache-acquia @@ -50,10 +50,6 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on -for cmd in php curl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done - -info "Started cache purging in Acquia." - extract_json_last_value() { local key="${1}" php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); \$last=array_pop(\$data); isset(\$last[\"${key}\"]) ? print trim(json_encode(\$last[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" @@ -64,6 +60,10 @@ extract_json_value() { php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); isset(\$data[\"${key}\"]) ? print trim(json_encode(\$data[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" } +for cmd in php curl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done + +info "Started cache purging in Acquia." + [ -z "${VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY}" ] && fail "Missing required value for VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY or VORTEX_ACQUIA_KEY." [ -z "${VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET}" ] && fail "Missing required value for VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." [ -z "${VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME}" ] && fail "Missing required value for VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME or VORTEX_ACQUIA_APP_NAME." From b005b8c5c17be0fa0376840c68658fdcb8dd8c9b Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 9 Sep 2026 10:15:26 +1000 Subject: [PATCH 02/11] [#3113] Placed the 'drush()' wrapper with the other definitions. --- .vortex/tooling/src/vortex-export-db-file | 4 ++-- .vortex/tooling/src/vortex-import-db-file | 4 ++-- .vortex/tooling/src/vortex-provision-sanitize-db | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.vortex/tooling/src/vortex-export-db-file b/.vortex/tooling/src/vortex-export-db-file index 489190440a..cf1d5936db 100755 --- a/.vortex/tooling/src/vortex-export-db-file +++ b/.vortex/tooling/src/vortex-export-db-file @@ -28,10 +28,10 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on -info "Started database file export." - drush() { ./vendor/bin/drush -y "$@"; } +info "Started database file export." + dump_file=$([ "${1:-}" ] && echo "${VORTEX_EXPORT_DB_FILE_DIR}/${1}" || echo "${VORTEX_EXPORT_DB_FILE_DIR}/export_db_$(date +%Y%m%d_%H%M%S).sql") # `drush sql:dump` resolves --result-file relative to the Drupal root, while diff --git a/.vortex/tooling/src/vortex-import-db-file b/.vortex/tooling/src/vortex-import-db-file index ef562ceeba..d992e50d39 100755 --- a/.vortex/tooling/src/vortex-import-db-file +++ b/.vortex/tooling/src/vortex-import-db-file @@ -25,10 +25,10 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on -info "Started database file import." - drush() { ./vendor/bin/drush -y "$@"; } +info "Started database file import." + dump_file="${1:-${VORTEX_IMPORT_DB_FILE_DIR}/${VORTEX_IMPORT_DB_FILE}}" if [ ! -f "${dump_file}" ]; then diff --git a/.vortex/tooling/src/vortex-provision-sanitize-db b/.vortex/tooling/src/vortex-provision-sanitize-db index c6e8ef4ea6..2024020fe8 100755 --- a/.vortex/tooling/src/vortex-provision-sanitize-db +++ b/.vortex/tooling/src/vortex-provision-sanitize-db @@ -34,10 +34,10 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on -info "Sanitizing database." - drush() { ./vendor/bin/drush -y "$@"; } +info "Sanitizing database." + drush sql:sanitize --sanitize-password="${VORTEX_PROVISION_SANITIZE_DB_PASSWORD}" --sanitize-email="${VORTEX_PROVISION_SANITIZE_DB_EMAIL}" pass "Sanitized database using drush sql:sanitize." From 8e4ec683bbad217cc5c8ed440631408092895947 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 9 Sep 2026 10:16:15 +1000 Subject: [PATCH 03/11] [#3113] Restored the boilerplate 'task()' and 'pass()' to the shipped form. --- .../content/contributing/maintenance/script-boilerplate.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vortex/docs/content/contributing/maintenance/script-boilerplate.sh b/.vortex/docs/content/contributing/maintenance/script-boilerplate.sh index e3e3ff2704..1855cd6a4e 100755 --- a/.vortex/docs/content/contributing/maintenance/script-boilerplate.sh +++ b/.vortex/docs/content/contributing/maintenance/script-boilerplate.sh @@ -17,8 +17,8 @@ VORTEX_EXAMPLE_URL="${VORTEX_EXAMPLE_URL:-http://example.com}" # @formatter:off info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[36m[INFO] %s\033[0m\n" "${1}" || printf "[INFO] %s\n" "${1}"; } note() { printf " %s\n" "${1}"; } -task() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[TASK] %s\033[0m\n" "${1}" || printf "[TASK] %s\n" "${1}"; } -pass() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s\033[0m\n" "${1}" || printf "[ OK ] %s\n" "${1}"; } +task() { _TASK_START=$(date +%s); [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[TASK] %s\033[0m\n" "${1}" || printf "[TASK] %s\n" "${1}"; } +pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START))s)" && unset _TASK_START; [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s%s\033[0m\n" "${1}" "${_d}" || printf "[ OK ] %s%s\n" "${1}" "${_d}"; } fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on From d0038f8671081a13bd66d5ad58b0fef54fea595e Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 9 Sep 2026 10:21:14 +1000 Subject: [PATCH 04/11] [#3113] Promoted the Acquia API error checks into the task scripts. --- .vortex/tooling/src/vortex-fetch-db-acquia | 4 +- .../tooling/src/vortex-task-copy-db-acquia | 45 ++++++++++++++++--- .../tooling/src/vortex-task-copy-files-acquia | 45 ++++++++++++++++--- .../src/vortex-task-purge-cache-acquia | 40 ++++++++++++++--- 4 files changed, 116 insertions(+), 18 deletions(-) diff --git a/.vortex/tooling/src/vortex-fetch-db-acquia b/.vortex/tooling/src/vortex-fetch-db-acquia index a401ab4d7d..18f862be14 100755 --- a/.vortex/tooling/src/vortex-fetch-db-acquia +++ b/.vortex/tooling/src/vortex-fetch-db-acquia @@ -113,7 +113,7 @@ if echo "${token_json}" | grep -q '"error"'; then fail "Authentication failed. Check VORTEX_FETCH_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_FETCH_DB_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET. API response: ${token_json}" fi -token="$(echo "${token_json}" | extract_json_value "access_token")" +token=$(echo "${token_json}" | extract_json_value "access_token") [ "${VORTEX_DEBUG-}" = "1" ] && note "Access token extracted (value redacted)." [ -z "${token}" ] && fail "Unable to retrieve a token. API response: ${token_json}" pass "Retrieved authentication token." @@ -141,7 +141,7 @@ fi env_id=$(echo "${envs_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "id") [ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted environment ID: ${env_id}" -[ -z "${env_id}" ] && fail "Unable to retrieve an environment ID for \"${VORTEX_FETCH_DB_ENVIRONMENT}\". API response: ${envs_json}" +[ -z "${env_id}" ] && fail "Unable to retrieve environment ID for \"${VORTEX_FETCH_DB_ENVIRONMENT}\". API response: ${envs_json}" pass "Retrieved ${VORTEX_FETCH_DB_ENVIRONMENT} environment ID." if [ "${VORTEX_FETCH_DB_FRESH}" = "1" ]; then diff --git a/.vortex/tooling/src/vortex-task-copy-db-acquia b/.vortex/tooling/src/vortex-task-copy-db-acquia index ef506c24d9..b1df41a9e6 100755 --- a/.vortex/tooling/src/vortex-task-copy-db-acquia +++ b/.vortex/tooling/src/vortex-task-copy-db-acquia @@ -78,26 +78,54 @@ for cmd in curl php; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} i task "Retrieving authentication token." token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_COPY_DB_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_COPY_DB_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") +[ "${VORTEX_DEBUG-}" = "1" ] && note "Token API response received (token redacted)." + +if echo "${token_json}" | grep -q '"error"'; then + fail "Authentication failed. Check VORTEX_TASK_COPY_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_COPY_DB_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET. API response: ${token_json}" +fi + token=$(echo "${token_json}" | extract_json_value "access_token") -[ -z "${token}" ] && fail "Unable to retrieve a token." +[ "${VORTEX_DEBUG-}" = "1" ] && note "Access token extracted (value redacted)." +[ -z "${token}" ] && fail "Unable to retrieve a token. API response: ${token_json}" pass "Retrieved authentication token." task "Retrieving ${VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME} application UUID." app_uuid_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications?filter=name%3D${VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME/ /%20}") +[ "${VORTEX_DEBUG-}" = "1" ] && note "Application API response: ${app_uuid_json}" + +if echo "${app_uuid_json}" | grep -q '"items":\[\]'; then + fail "Application \"${VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME}\" not found. Check application name and access permissions." +fi + app_uuid=$(echo "${app_uuid_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "uuid") -[ -z "${app_uuid}" ] && fail "Unable to retrieve an application UUID." +[ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted app UUID: ${app_uuid}" +[ -z "${app_uuid}" ] && fail "Unable to retrieve an application UUID for \"${VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME}\". API response: ${app_uuid_json}" pass "Retrieved ${VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME} application UUID." task "Retrieving source environment ID." envs_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications/${app_uuid}/environments?filter=name%3D${VORTEX_TASK_COPY_DB_ACQUIA_SRC}") +[ "${VORTEX_DEBUG-}" = "1" ] && note "Environments API response: ${envs_json}" + +if echo "${envs_json}" | grep -q '"items":\[\]'; then + fail "Environment \"${VORTEX_TASK_COPY_DB_ACQUIA_SRC}\" not found in application \"${VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME}\". Check environment name." +fi + src_env_id=$(echo "${envs_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "id") -[ -z "${src_env_id}" ] && fail "Unable to retrieve source environment ID." +[ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted source environment ID: ${src_env_id}" +[ -z "${src_env_id}" ] && fail "Unable to retrieve source environment ID for \"${VORTEX_TASK_COPY_DB_ACQUIA_SRC}\". API response: ${envs_json}" pass "Retrieved source environment ID." task "Retrieving destination environment ID." envs_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications/${app_uuid}/environments?filter=name%3D${VORTEX_TASK_COPY_DB_ACQUIA_DST}") +[ "${VORTEX_DEBUG-}" = "1" ] && note "Environments API response: ${envs_json}" + +if echo "${envs_json}" | grep -q '"items":\[\]'; then + fail "Environment \"${VORTEX_TASK_COPY_DB_ACQUIA_DST}\" not found in application \"${VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME}\". Check environment name." +fi + dst_env_id=$(echo "${envs_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "id") -[ -z "${dst_env_id}" ] && fail "Unable to retrieve destination environment ID." +[ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted destination environment ID: ${dst_env_id}" +[ -z "${dst_env_id}" ] && fail "Unable to retrieve destination environment ID for \"${VORTEX_TASK_COPY_DB_ACQUIA_DST}\". API response: ${envs_json}" pass "Retrieved destination environment ID." task "Copying database from ${VORTEX_TASK_COPY_DB_ACQUIA_SRC} to ${VORTEX_TASK_COPY_DB_ACQUIA_DST} environment." @@ -115,8 +143,15 @@ for i in $(seq 1 "${VORTEX_TASK_COPY_DB_ACQUIA_STATUS_RETRIES}"); do [ "${task_state}" = "completed" ] && task_completed=1 && break token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_COPY_DB_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_COPY_DB_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") + [ "${VORTEX_DEBUG-}" = "1" ] && note "Token API response received (token redacted)." + + if echo "${token_json}" | grep -q '"error"'; then + fail "Authentication failed. Check VORTEX_TASK_COPY_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_COPY_DB_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET. API response: ${token_json}" + fi + token=$(echo "${token_json}" | extract_json_value "access_token") - [ -z "${token}" ] && fail "Unable to retrieve a token." + [ "${VORTEX_DEBUG-}" = "1" ] && note "Access token extracted (value redacted)." + [ -z "${token}" ] && fail "Unable to retrieve a token. API response: ${token_json}" note "Refreshed authentication token." done diff --git a/.vortex/tooling/src/vortex-task-copy-files-acquia b/.vortex/tooling/src/vortex-task-copy-files-acquia index 6d856060ba..baba63bcdd 100755 --- a/.vortex/tooling/src/vortex-task-copy-files-acquia +++ b/.vortex/tooling/src/vortex-task-copy-files-acquia @@ -74,26 +74,54 @@ info "Started files copying between environments in Acquia." task "Retrieving authentication token." token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_COPY_FILES_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_COPY_FILES_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") +[ "${VORTEX_DEBUG-}" = "1" ] && note "Token API response received (token redacted)." + +if echo "${token_json}" | grep -q '"error"'; then + fail "Authentication failed. Check VORTEX_TASK_COPY_FILES_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_COPY_FILES_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET. API response: ${token_json}" +fi + token=$(echo "${token_json}" | extract_json_value "access_token") -[ -z "${token}" ] && fail "Unable to retrieve a token." +[ "${VORTEX_DEBUG-}" = "1" ] && note "Access token extracted (value redacted)." +[ -z "${token}" ] && fail "Unable to retrieve a token. API response: ${token_json}" pass "Retrieved authentication token." task "Retrieving ${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME} application UUID." app_uuid_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications?filter=name%3D${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME/ /%20}") +[ "${VORTEX_DEBUG-}" = "1" ] && note "Application API response: ${app_uuid_json}" + +if echo "${app_uuid_json}" | grep -q '"items":\[\]'; then + fail "Application \"${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME}\" not found. Check application name and access permissions." +fi + app_uuid=$(echo "${app_uuid_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "uuid") -[ -z "${app_uuid}" ] && fail "Unable to retrieve an application UUID." +[ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted app UUID: ${app_uuid}" +[ -z "${app_uuid}" ] && fail "Unable to retrieve an application UUID for \"${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME}\". API response: ${app_uuid_json}" pass "Retrieved ${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME} application UUID." task "Retrieving source environment ID." envs_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications/${app_uuid}/environments?filter=name%3D${VORTEX_TASK_COPY_FILES_ACQUIA_SRC}") +[ "${VORTEX_DEBUG-}" = "1" ] && note "Environments API response: ${envs_json}" + +if echo "${envs_json}" | grep -q '"items":\[\]'; then + fail "Environment \"${VORTEX_TASK_COPY_FILES_ACQUIA_SRC}\" not found in application \"${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME}\". Check environment name." +fi + src_env_id=$(echo "${envs_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "id") -[ -z "${src_env_id}" ] && fail "Unable to retrieve source environment ID." +[ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted source environment ID: ${src_env_id}" +[ -z "${src_env_id}" ] && fail "Unable to retrieve source environment ID for \"${VORTEX_TASK_COPY_FILES_ACQUIA_SRC}\". API response: ${envs_json}" pass "Retrieved source environment ID." task "Retrieving destination environment ID." envs_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications/${app_uuid}/environments?filter=name%3D${VORTEX_TASK_COPY_FILES_ACQUIA_DST}") +[ "${VORTEX_DEBUG-}" = "1" ] && note "Environments API response: ${envs_json}" + +if echo "${envs_json}" | grep -q '"items":\[\]'; then + fail "Environment \"${VORTEX_TASK_COPY_FILES_ACQUIA_DST}\" not found in application \"${VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME}\". Check environment name." +fi + dst_env_id=$(echo "${envs_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "id") -[ -z "${dst_env_id}" ] && fail "Unable to retrieve destination environment ID." +[ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted destination environment ID: ${dst_env_id}" +[ -z "${dst_env_id}" ] && fail "Unable to retrieve destination environment ID for \"${VORTEX_TASK_COPY_FILES_ACQUIA_DST}\". API response: ${envs_json}" pass "Retrieved destination environment ID." task "Copying files from ${VORTEX_TASK_COPY_FILES_ACQUIA_SRC} to ${VORTEX_TASK_COPY_FILES_ACQUIA_DST} environment." @@ -111,8 +139,15 @@ for i in $(seq 1 "${VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_RETRIES}"); do [ "${task_state}" = "completed" ] && task_completed=1 && break token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_COPY_FILES_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_COPY_FILES_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") + [ "${VORTEX_DEBUG-}" = "1" ] && note "Token API response received (token redacted)." + + if echo "${token_json}" | grep -q '"error"'; then + fail "Authentication failed. Check VORTEX_TASK_COPY_FILES_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_COPY_FILES_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET. API response: ${token_json}" + fi + token=$(echo "${token_json}" | extract_json_value "access_token") - [ -z "${token}" ] && fail "Unable to retrieve a token." + [ "${VORTEX_DEBUG-}" = "1" ] && note "Access token extracted (value redacted)." + [ -z "${token}" ] && fail "Unable to retrieve a token. API response: ${token_json}" note "Refreshed authentication token." done diff --git a/.vortex/tooling/src/vortex-task-purge-cache-acquia b/.vortex/tooling/src/vortex-task-purge-cache-acquia index d719e34fb3..5d5c941f41 100755 --- a/.vortex/tooling/src/vortex-task-purge-cache-acquia +++ b/.vortex/tooling/src/vortex-task-purge-cache-acquia @@ -74,20 +74,41 @@ info "Started cache purging in Acquia." task "Retrieving authentication token." token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") +[ "${VORTEX_DEBUG-}" = "1" ] && note "Token API response received (token redacted)." + +if echo "${token_json}" | grep -q '"error"'; then + fail "Authentication failed. Check VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET. API response: ${token_json}" +fi + token=$(echo "${token_json}" | extract_json_value "access_token") -[ -z "${token}" ] && fail "Unable to retrieve a token." +[ "${VORTEX_DEBUG-}" = "1" ] && note "Access token extracted (value redacted)." +[ -z "${token}" ] && fail "Unable to retrieve a token. API response: ${token_json}" pass "Retrieved authentication token." task "Retrieving ${VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME} application UUID." app_uuid_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications?filter=name%3D${VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME/ /%20}") +[ "${VORTEX_DEBUG-}" = "1" ] && note "Application API response: ${app_uuid_json}" + +if echo "${app_uuid_json}" | grep -q '"items":\[\]'; then + fail "Application \"${VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME}\" not found. Check application name and access permissions." +fi + app_uuid=$(echo "${app_uuid_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "uuid") -[ -z "${app_uuid}" ] && fail "Unable to retrieve an application UUID." +[ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted app UUID: ${app_uuid}" +[ -z "${app_uuid}" ] && fail "Unable to retrieve an application UUID for \"${VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME}\". API response: ${app_uuid_json}" pass "Retrieved ${VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME} application UUID." task "Retrieving environment ID." envs_json=$(curl -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" "https://cloud.acquia.com/api/applications/${app_uuid}/environments?filter=name%3D${VORTEX_TASK_PURGE_CACHE_ACQUIA_ENV}") -ENV_ID=$(echo "${envs_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "id") -[ -z "${ENV_ID}" ] && fail "Unable to retrieve environment ID." +[ "${VORTEX_DEBUG-}" = "1" ] && note "Environments API response: ${envs_json}" + +if echo "${envs_json}" | grep -q '"items":\[\]'; then + fail "Environment \"${VORTEX_TASK_PURGE_CACHE_ACQUIA_ENV}\" not found in application \"${VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME}\". Check environment name." +fi + +env_id=$(echo "${envs_json}" | extract_json_value "_embedded" | extract_json_value "items" | extract_json_last_value "id") +[ "${VORTEX_DEBUG-}" = "1" ] && note "Extracted environment ID: ${env_id}" +[ -z "${env_id}" ] && fail "Unable to retrieve environment ID for \"${VORTEX_TASK_PURGE_CACHE_ACQUIA_ENV}\". API response: ${envs_json}" pass "Retrieved environment ID." task "Compiling a list of domains." @@ -128,7 +149,7 @@ if [ "${#domain_list[@]}" -gt 0 ]; then # are cleared per domain and a missing domain is not a failure. for domain in "${domain_list[@]}"; do task "Purging cache for ${VORTEX_TASK_PURGE_CACHE_ACQUIA_ENV} environment domain ${domain}." - task_status_json=$(curl -X POST -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" -H "Content-Type: application/json" -d "{\"domains\":[\"${domain}\"]}" "https://cloud.acquia.com/api/environments/${ENV_ID}/domains/actions/clear-varnish") + task_status_json=$(curl -X POST -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" -H "Content-Type: application/json" -d "{\"domains\":[\"${domain}\"]}" "https://cloud.acquia.com/api/environments/${env_id}/domains/actions/clear-varnish") notification_url=$(echo "${task_status_json}" | extract_json_value "_links" | extract_json_value "notification" | extract_json_value "href") || true # A missing domain yields an empty notification URL and is skipped @@ -154,8 +175,15 @@ if [ "${#domain_list[@]}" -gt 0 ]; then fi token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") + [ "${VORTEX_DEBUG-}" = "1" ] && note "Token API response received (token redacted)." + + if echo "${token_json}" | grep -q '"error"'; then + fail "Authentication failed. Check VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET. API response: ${token_json}" + fi + token=$(echo "${token_json}" | extract_json_value "access_token") - [ -z "${token}" ] && fail "Unable to retrieve a token." + [ "${VORTEX_DEBUG-}" = "1" ] && note "Access token extracted (value redacted)." + [ -z "${token}" ] && fail "Unable to retrieve a token. API response: ${token_json}" note "Refreshed authentication token." done echo From a6917f07e5de9bb8df58aa34eae94826b527ccf2 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 9 Sep 2026 10:27:16 +1000 Subject: [PATCH 05/11] [#3113] Converged the Lagoon CLI flags and the command availability checks. --- .vortex/tooling/src/vortex-fetch-db-s3 | 50 ++++++++++----- .vortex/tooling/src/vortex-notify-diffy | 2 +- .vortex/tooling/src/vortex-push-db-s3 | 64 +++++++++++-------- .../tooling/src/vortex-task-copy-db-acquia | 4 +- .vortex/tooling/src/vortex-task-custom-lagoon | 8 +-- 5 files changed, 79 insertions(+), 49 deletions(-) diff --git a/.vortex/tooling/src/vortex-fetch-db-s3 b/.vortex/tooling/src/vortex-fetch-db-s3 index b7703a5a5f..d45d027c65 100755 --- a/.vortex/tooling/src/vortex-fetch-db-s3 +++ b/.vortex/tooling/src/vortex-fetch-db-s3 @@ -59,6 +59,27 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on +hash_sha256() { printf '%s' "${1}" | openssl dgst -sha256 | sed 's/^.* //'; } +hmac_sha256() { printf '%s' "${2}" | openssl dgst -sha256 -mac HMAC -macopt "${1}" | sed 's/^.* //'; } + +# Sign a string with a key derived for the given date, region and service, as +# AWS Signature Version 4 requires. +aws_signature() { + local secret_key="${1}" + local date="${2}" + local region="${3}" + local service_name="${4}" + local string_to_sign="${5}" + + local date_key region_key service_key signing_key + date_key=$(hmac_sha256 "key:AWS4${secret_key}" "${date}") + region_key=$(hmac_sha256 "hexkey:${date_key}" "${region}") + service_key=$(hmac_sha256 "hexkey:${region_key}" "${service_name}") + signing_key=$(hmac_sha256 "hexkey:${service_key}" "aws4_request") + + hmac_sha256 "hexkey:${signing_key}" "${string_to_sign}" +} + for cmd in curl openssl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done [ -z "${VORTEX_FETCH_DB_S3_ACCESS_KEY}" ] && fail "Missing required value for VORTEX_FETCH_DB_S3_ACCESS_KEY." @@ -89,10 +110,7 @@ note "S3 bucket: ${VORTEX_FETCH_DB_S3_BUCKET}" note "S3 region: ${VORTEX_FETCH_DB_S3_REGION}" [ -n "${VORTEX_FETCH_DB_S3_PREFIX}" ] && note "S3 prefix: ${VORTEX_FETCH_DB_S3_PREFIX}" -hash_sha256() { printf '%b' "${1}" | openssl dgst -sha256 | sed 's/^.* //'; } -hmac_sha256() { printf '%s' "${2}" | openssl dgst -sha256 -mac HMAC -macopt "${1}" | sed 's/^.* //'; } - -payload_hash="$(printf "" | openssl dgst -sha256 | sed 's/^.* //')" +payload_hash=$(hash_sha256 "") headers="content-type:${content_type} host:${host} @@ -100,23 +118,21 @@ x-amz-content-sha256:${payload_hash} x-amz-date:${date_long}" signed_headers="content-type;host;x-amz-content-sha256;x-amz-date" -request="${request_type} -${uri}\n -${headers}\n + +canonical_request="${request_type} +${uri} + +${headers} + ${signed_headers} ${payload_hash}" -create_signature() { - string_to_sign="${auth_type}\n${date_long}\n${date_short}/${VORTEX_FETCH_DB_S3_REGION}/${service}/aws4_request\n$(hash_sha256 "${request}")" - date_key=$(hmac_sha256 key:"AWS4${VORTEX_FETCH_DB_S3_SECRET_KEY}" "${date_short}") - region_key=$(hmac_sha256 hexkey:"${date_key}" "${VORTEX_FETCH_DB_S3_REGION}") - service_key=$(hmac_sha256 hexkey:"${region_key}" "${service}") - signing_key=$(hmac_sha256 hexkey:"${service_key}" "aws4_request") - - printf '%b' "${string_to_sign}" | openssl dgst -sha256 -mac HMAC -macopt hexkey:"${signing_key}" | sed 's/(stdin)= //' | sed 's/SHA2-256//' -} +string_to_sign="${auth_type} +${date_long} +${date_short}/${VORTEX_FETCH_DB_S3_REGION}/${service}/aws4_request +$(hash_sha256 "${canonical_request}")" -signature="$(create_signature)" +signature=$(aws_signature "${VORTEX_FETCH_DB_S3_SECRET_KEY}" "${date_short}" "${VORTEX_FETCH_DB_S3_REGION}" "${service}" "${string_to_sign}") auth_header="\ ${auth_type} Credential=${VORTEX_FETCH_DB_S3_ACCESS_KEY}/${date_short}/\ ${VORTEX_FETCH_DB_S3_REGION}/${service}/aws4_request, \ diff --git a/.vortex/tooling/src/vortex-notify-diffy b/.vortex/tooling/src/vortex-notify-diffy index be1a3d2d9a..0dff63c136 100755 --- a/.vortex/tooling/src/vortex-notify-diffy +++ b/.vortex/tooling/src/vortex-notify-diffy @@ -68,7 +68,7 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on -for cmd in curl php; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done +for cmd in php curl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done info "Started Diffy notification." diff --git a/.vortex/tooling/src/vortex-push-db-s3 b/.vortex/tooling/src/vortex-push-db-s3 index 3bead90eff..93470ad6ed 100755 --- a/.vortex/tooling/src/vortex-push-db-s3 +++ b/.vortex/tooling/src/vortex-push-db-s3 @@ -50,6 +50,42 @@ pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START) fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on +# URL-encode the object key for signing and the request URL, preserving "/". +uri_encode_key() { + _s="${1}" + _out="" + while [ -n "${_s}" ]; do + _c="${_s:0:1}" + case "${_c}" in + [a-zA-Z0-9._~/-]) _out="${_out}${_c}" ;; + *) _out="${_out}$(printf '%%%02X' "'${_c}")" ;; + esac + _s="${_s:1}" + done + printf '%s' "${_out}" +} + +hash_sha256() { printf '%s' "${1}" | openssl dgst -sha256 | sed 's/^.* //'; } +hmac_sha256() { printf '%s' "${2}" | openssl dgst -sha256 -mac HMAC -macopt "${1}" | sed 's/^.* //'; } + +# Sign a string with a key derived for the given date, region and service, as +# AWS Signature Version 4 requires. +aws_signature() { + local secret_key="${1}" + local date="${2}" + local region="${3}" + local service_name="${4}" + local string_to_sign="${5}" + + local date_key region_key service_key signing_key + date_key=$(hmac_sha256 "key:AWS4${secret_key}" "${date}") + region_key=$(hmac_sha256 "hexkey:${date_key}" "${region}") + service_key=$(hmac_sha256 "hexkey:${region_key}" "${service_name}") + signing_key=$(hmac_sha256 "hexkey:${service_key}" "aws4_request") + + hmac_sha256 "hexkey:${signing_key}" "${string_to_sign}" +} + for cmd in curl openssl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done [ -z "${VORTEX_PUSH_DB_S3_ACCESS_KEY}" ] && fail "Missing required value for VORTEX_PUSH_DB_S3_ACCESS_KEY." @@ -71,20 +107,6 @@ base_url=".${service}.${VORTEX_PUSH_DB_S3_REGION}.amazonaws.com" date_short=$(date -u +'%Y%m%d') date_long=$(date -u +'%Y%m%dT%H%M%SZ') -# URL-encode the object key for signing and the request URL, preserving "/". -uri_encode_key() { - _s="${1}" - _out="" - while [ -n "${_s}" ]; do - _c="${_s:0:1}" - case "${_c}" in - [a-zA-Z0-9._~/-]) _out="${_out}${_c}" ;; - *) _out="${_out}$(printf '%%%02X' "'${_c}")" ;; - esac - _s="${_s:1}" - done - printf '%s' "${_out}" -} object_key="$(uri_encode_key "${VORTEX_PUSH_DB_S3_PREFIX}${VORTEX_PUSH_DB_S3_REMOTE_FILE}")" object_url="https://${VORTEX_PUSH_DB_S3_BUCKET}${base_url}/${object_key}" @@ -101,15 +123,7 @@ else content_type='application/octet-stream' fi -payload_hash=$(openssl dgst -sha256 -hex <"${local_file}" 2>/dev/null | sed 's/^.* //') - -aws_sign4() { - l_date=$(printf '%s' "${2}" | openssl dgst -sha256 -hex -mac HMAC -macopt "key:AWS4${1}" 2>/dev/null | sed 's/^.* //') - l_region=$(printf '%s' "${3}" | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${l_date}" 2>/dev/null | sed 's/^.* //') - l_service=$(printf '%s' "${4}" | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${l_region}" 2>/dev/null | sed 's/^.* //') - l_signing=$(printf 'aws4_request' | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${l_service}" 2>/dev/null | sed 's/^.* //') - printf '%s' "${5}" | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${l_signing}" 2>/dev/null | sed 's/^.* //' -} +payload_hash=$(openssl dgst -sha256 <"${local_file}" | sed 's/^.* //') header_list='content-type;host;x-amz-content-sha256;x-amz-date;x-amz-server-side-encryption;x-amz-storage-class' @@ -127,7 +141,7 @@ x-amz-storage-class:${VORTEX_PUSH_DB_S3_STORAGE_CLASS} ${header_list} ${payload_hash}" -canonical_request_hash=$(printf '%s' "${canonical_request}" | openssl dgst -sha256 -hex 2>/dev/null | sed 's/^.* //') +canonical_request_hash=$(hash_sha256 "${canonical_request}") string_to_sign="\ ${auth_type} @@ -135,7 +149,7 @@ ${date_long} ${date_short}/${VORTEX_PUSH_DB_S3_REGION}/${service}/aws4_request ${canonical_request_hash}" -signature=$(aws_sign4 "${VORTEX_PUSH_DB_S3_SECRET_KEY}" "${date_short}" "${VORTEX_PUSH_DB_S3_REGION}" "${service}" "${string_to_sign}") +signature=$(aws_signature "${VORTEX_PUSH_DB_S3_SECRET_KEY}" "${date_short}" "${VORTEX_PUSH_DB_S3_REGION}" "${service}" "${string_to_sign}") set +e response=$(curl --silent --show-error --location --proto-redir =https \ diff --git a/.vortex/tooling/src/vortex-task-copy-db-acquia b/.vortex/tooling/src/vortex-task-copy-db-acquia index b1df41a9e6..302f6213cc 100755 --- a/.vortex/tooling/src/vortex-task-copy-db-acquia +++ b/.vortex/tooling/src/vortex-task-copy-db-acquia @@ -63,9 +63,9 @@ extract_json_value() { php -r "\$data=json_decode(file_get_contents('php://stdin'), TRUE); isset(\$data[\"${key}\"]) ? print trim(json_encode(\$data[\"${key}\"], JSON_UNESCAPED_SLASHES), '\"') : exit(1);" } -info "Started database copying between environments in Acquia." +for cmd in php curl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done -for cmd in curl php; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done +info "Started database copying between environments in Acquia." [ -z "${VORTEX_TASK_COPY_DB_ACQUIA_KEY}" ] && fail "Missing required value for VORTEX_TASK_COPY_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY." [ -z "${VORTEX_TASK_COPY_DB_ACQUIA_SECRET}" ] && fail "Missing required value for VORTEX_TASK_COPY_DB_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." diff --git a/.vortex/tooling/src/vortex-task-custom-lagoon b/.vortex/tooling/src/vortex-task-custom-lagoon index a780662e8f..2f0a584d80 100755 --- a/.vortex/tooling/src/vortex-task-custom-lagoon +++ b/.vortex/tooling/src/vortex-task-custom-lagoon @@ -87,17 +87,17 @@ if ! command -v lagoon >/dev/null || [ -n "${VORTEX_TASK_CUSTOM_LAGOON_CLI_FORCE pass "Installed Lagoon CLI." fi -for cmd in curl lagoon; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done +for cmd in lagoon curl; do command -v "${cmd}" >/dev/null || fail "Command ${cmd} is not available."; done task "Configuring Lagoon instance." # shellcheck disable=SC2218 -lagoon config add --force -l "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE}" -g "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_GRAPHQL}" -H "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_HOSTNAME}" -P "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_PORT}" +lagoon config add --force --lagoon "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE}" --graphql "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_GRAPHQL}" --hostname "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_HOSTNAME}" --port "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE_PORT}" pass "Configured Lagoon instance." -lagoon() { command lagoon --force --skip-update-check -i "${VORTEX_TASK_CUSTOM_LAGOON_SSH_FILE}" -l "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE}" -p "${VORTEX_TASK_CUSTOM_LAGOON_PROJECT}" "$@"; } +lagoon() { command lagoon --force --skip-update-check --ssh-key "${VORTEX_TASK_CUSTOM_LAGOON_SSH_FILE}" --lagoon "${VORTEX_TASK_CUSTOM_LAGOON_INSTANCE}" --project "${VORTEX_TASK_CUSTOM_LAGOON_PROJECT}" "$@"; } task "Creating ${VORTEX_TASK_CUSTOM_LAGOON_NAME} task: project: ${VORTEX_TASK_CUSTOM_LAGOON_PROJECT}, branch: ${VORTEX_TASK_CUSTOM_LAGOON_BRANCH}." -lagoon run custom -e "${VORTEX_TASK_CUSTOM_LAGOON_BRANCH}" -N "${VORTEX_TASK_CUSTOM_LAGOON_NAME}" -c "${VORTEX_TASK_CUSTOM_LAGOON_COMMAND}" +lagoon run custom --environment "${VORTEX_TASK_CUSTOM_LAGOON_BRANCH}" --name "${VORTEX_TASK_CUSTOM_LAGOON_NAME}" --command "${VORTEX_TASK_CUSTOM_LAGOON_COMMAND}" pass "Created ${VORTEX_TASK_CUSTOM_LAGOON_NAME} task: project: ${VORTEX_TASK_CUSTOM_LAGOON_PROJECT}, branch: ${VORTEX_TASK_CUSTOM_LAGOON_BRANCH}." pass "Finished Lagoon task ${VORTEX_TASK_CUSTOM_LAGOON_NAME}." From 996b6d2babdc224befef6d23dfc84afa18d75f91 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 9 Sep 2026 10:29:06 +1000 Subject: [PATCH 06/11] [#3113] Extracted the repeated override-database block in the Lagoon deploy. --- .vortex/tooling/src/vortex-deploy-lagoon | 191 ++++++++++------------- 1 file changed, 83 insertions(+), 108 deletions(-) diff --git a/.vortex/tooling/src/vortex-deploy-lagoon b/.vortex/tooling/src/vortex-deploy-lagoon index 06e99bb025..b1157ca509 100755 --- a/.vortex/tooling/src/vortex-deploy-lagoon +++ b/.vortex/tooling/src/vortex-deploy-lagoon @@ -138,12 +138,89 @@ close_deploy() { fail "Failed to request ${1}" "${exit_code}" } +# Read the database import override flag on an environment into +# ${override_db_state}, ${override_db_original} and ${override_db_scope}. +discover_override_db() { + local environment="${1}" + local override_db_vars override_db_count + + # The flag is set for a single build and then restored to its previous + # state, so an environment that had no flag is not left with one. + task "Discovering a database import override flag." + override_db_state="unknown" + override_db_original="" + override_db_scope="global" + + if override_db_vars="$(lagoon list variables --environment "${environment}" --reveal --output-json 2>/dev/null)"; then + if override_db_count="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB")] | length' 2>/dev/null)"; then + if [ "${override_db_count}" = "0" ]; then + override_db_state="absent" + else + override_db_state="present" + override_db_original="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .value] | first // ""')" + override_db_scope="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .scope] | first // "global"')" + fi + fi + fi + + if [ "${override_db_state}" = "unknown" ]; then + # An unreadable variable list is not an absent flag: adding one and + # deleting it afterwards would destroy a flag that already exists, so + # the flag is left alone. + note "WARNING: Could not read environment variables. A database import override flag was left unchanged and may not match the requested deployment action." + elif [ "${override_db_state}" = "absent" ]; then + note "No existing database import override flag found." + else + note "Found an existing database import override flag with value \"${override_db_original}\" and scope \"${override_db_scope}\"." + fi + + pass "Completed database import override flag discovery." +} + +# Write the flag this deployment needs onto an environment. A flag that was not +# there is added, one that was there is updated in its own scope. +apply_override_db() { + local environment="${1}" + local value="${2}" + + if [ "${override_db_state}" = "absent" ]; then + task "Adding a database import override flag with value ${value}." + lagoon add variable --environment "${environment}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${value}" --scope global || true + pass "Added a database import override flag with value ${value}." + elif [ "${override_db_state}" = "present" ]; then + task "Updating a database import override flag to ${value}." + lagoon update variable --environment "${environment}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${value}" --scope "${override_db_scope}" || true + pass "Updated a database import override flag to ${value}." + fi +} + +# Return the flag to the state discovery found it in. +restore_override_db() { + local environment="${1}" + + if [ "${override_db_state}" = "absent" ]; then + lagoon delete variable --environment "${environment}" --name VORTEX_PROVISION_OVERRIDE_DB || true + note "Removed a database import override flag." + elif [ "${override_db_state}" = "present" ]; then + lagoon update variable --environment "${environment}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_original}" --scope "${override_db_scope}" || true + note "Restored a database import override flag to ${override_db_original}." + fi +} + # Deploy status, carried from run_lagoon_deploy to close_deploy. exit_code=0 # Raw CLI output of a failed deploy, printed with the failure line. deploy_error="" +# The database import override flag value this deployment needs while Lagoon +# queues the build. +if [ "${VORTEX_DEPLOY_LAGOON_ACTION}" = "deploy_override_db" ]; then + override_db_value=1 +else + override_db_value=0 +fi + info "Started Lagoon deployment." if [ "${VORTEX_DEPLOY_MODE}" = "tag" ]; then @@ -211,53 +288,8 @@ else pass "Completed environment discovery." if [ "${is_redeploy:-}" = "1" ]; then - # The flag value this deployment needs while Lagoon queues the build. - if [ "${VORTEX_DEPLOY_LAGOON_ACTION}" = "deploy_override_db" ]; then - override_db_value=1 - else - override_db_value=0 - fi - - # The flag is set for a single build and then restored to its previous - # state, so an environment that had no flag is not left with one. - task "Discovering a database import override flag." - override_db_state="unknown" - override_db_original="" - override_db_scope="global" - - if override_db_vars="$(lagoon list variables --environment "${deploy_pr_full}" --reveal --output-json 2>/dev/null)"; then - if override_db_count="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB")] | length' 2>/dev/null)"; then - if [ "${override_db_count}" = "0" ]; then - override_db_state="absent" - else - override_db_state="present" - override_db_original="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .value] | first // ""')" - override_db_scope="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .scope] | first // "global"')" - fi - fi - fi - - if [ "${override_db_state}" = "unknown" ]; then - # An unreadable variable list is not an absent flag: adding one and - # deleting it afterwards would destroy a flag that already exists, so - # the flag is left alone. - note "WARNING: Could not read environment variables. A database import override flag was left unchanged and may not match the requested deployment action." - elif [ "${override_db_state}" = "absent" ]; then - note "No existing database import override flag found." - else - note "Found an existing database import override flag with value \"${override_db_original}\" and scope \"${override_db_scope}\"." - fi - pass "Completed database import override flag discovery." - - if [ "${override_db_state}" = "absent" ]; then - task "Adding a database import override flag with value ${override_db_value}." - lagoon add variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_value}" --scope global || true - pass "Added a database import override flag with value ${override_db_value}." - elif [ "${override_db_state}" = "present" ]; then - task "Updating a database import override flag to ${override_db_value}." - lagoon update variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_value}" --scope "${override_db_scope}" || true - pass "Updated a database import override flag to ${override_db_value}." - fi + discover_override_db "${deploy_pr_full}" + apply_override_db "${deploy_pr_full}" "${override_db_value}" task "Redeploying environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, PR: ${VORTEX_DEPLOY_LAGOON_PR}." run_lagoon_deploy deploy pullrequest --number "${VORTEX_DEPLOY_LAGOON_PR}" --base-branch-name "${VORTEX_DEPLOY_LAGOON_PR_BASE_BRANCH}" --base-branch-ref "origin/${VORTEX_DEPLOY_LAGOON_PR_BASE_BRANCH}" --head-branch-name "${VORTEX_DEPLOY_LAGOON_BRANCH}" --head-branch-ref "${VORTEX_DEPLOY_LAGOON_PR_HEAD}" --title "${deploy_pr_full}" @@ -268,13 +300,7 @@ else note "Waiting for deployment to be queued." sleep 10 - if [ "${override_db_state}" = "absent" ]; then - lagoon delete variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB || true - note "Removed a database import override flag." - elif [ "${override_db_state}" = "present" ]; then - lagoon update variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_original}" --scope "${override_db_scope}" || true - note "Restored a database import override flag to ${override_db_original}." - fi + restore_override_db "${deploy_pr_full}" close_deploy "redeployment of environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, PR: ${VORTEX_DEPLOY_LAGOON_PR}." @@ -306,53 +332,8 @@ else pass "Completed environment discovery." if [ "${is_redeploy:-}" = "1" ]; then - # The flag value this deployment needs while Lagoon queues the build. - if [ "${VORTEX_DEPLOY_LAGOON_ACTION}" = "deploy_override_db" ]; then - override_db_value=1 - else - override_db_value=0 - fi - - # The flag is set for a single build and then restored to its previous - # state, so an environment that had no flag is not left with one. - task "Discovering a database import override flag." - override_db_state="unknown" - override_db_original="" - override_db_scope="global" - - if override_db_vars="$(lagoon list variables --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --reveal --output-json 2>/dev/null)"; then - if override_db_count="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB")] | length' 2>/dev/null)"; then - if [ "${override_db_count}" = "0" ]; then - override_db_state="absent" - else - override_db_state="present" - override_db_original="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .value] | first // ""')" - override_db_scope="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .scope] | first // "global"')" - fi - fi - fi - - if [ "${override_db_state}" = "unknown" ]; then - # An unreadable variable list is not an absent flag: adding one and - # deleting it afterwards would destroy a flag that already exists, so - # the flag is left alone. - note "WARNING: Could not read environment variables. A database import override flag was left unchanged and may not match the requested deployment action." - elif [ "${override_db_state}" = "absent" ]; then - note "No existing database import override flag found." - else - note "Found an existing database import override flag with value \"${override_db_original}\" and scope \"${override_db_scope}\"." - fi - pass "Completed database import override flag discovery." - - if [ "${override_db_state}" = "absent" ]; then - task "Adding a database import override flag with value ${override_db_value}." - lagoon add variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_value}" --scope global || true - pass "Added a database import override flag with value ${override_db_value}." - elif [ "${override_db_state}" = "present" ]; then - task "Updating a database import override flag to ${override_db_value}." - lagoon update variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_value}" --scope "${override_db_scope}" || true - pass "Updated a database import override flag to ${override_db_value}." - fi + discover_override_db "${VORTEX_DEPLOY_LAGOON_BRANCH}" + apply_override_db "${VORTEX_DEPLOY_LAGOON_BRANCH}" "${override_db_value}" task "Redeploying environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}." run_lagoon_deploy deploy latest --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" @@ -363,13 +344,7 @@ else note "Waiting for deployment to be queued." sleep 10 - if [ "${override_db_state}" = "absent" ]; then - lagoon delete variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB || true - note "Removed a database import override flag." - elif [ "${override_db_state}" = "present" ]; then - lagoon update variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_original}" --scope "${override_db_scope}" || true - note "Restored a database import override flag to ${override_db_original}." - fi + restore_override_db "${VORTEX_DEPLOY_LAGOON_BRANCH}" close_deploy "redeployment of environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}." From 8eb8a1015e0807a628a6a0958a777c4c635ee103 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 9 Sep 2026 10:38:01 +1000 Subject: [PATCH 07/11] [#3113] Covered the Acquia copy tasks and the Lagoon custom task with BATS. --- .../tests/unit/task-copy-db-acquia.bats | 170 ++++++++++++++++++ .../tests/unit/task-copy-files-acquia.bats | 170 ++++++++++++++++++ .../tests/unit/task-custom-lagoon.bats | 149 +++++++++++++++ 3 files changed, 489 insertions(+) create mode 100644 .vortex/tooling/tests/unit/task-copy-db-acquia.bats create mode 100644 .vortex/tooling/tests/unit/task-copy-files-acquia.bats create mode 100644 .vortex/tooling/tests/unit/task-custom-lagoon.bats diff --git a/.vortex/tooling/tests/unit/task-copy-db-acquia.bats b/.vortex/tooling/tests/unit/task-copy-db-acquia.bats new file mode 100644 index 0000000000..8ad0e0bf81 --- /dev/null +++ b/.vortex/tooling/tests/unit/task-copy-db-acquia.bats @@ -0,0 +1,170 @@ +#!/usr/bin/env bats +## +# Unit tests for the 'vortex-task-copy-db-acquia' script. +# +# shellcheck disable=SC2030,SC2031,SC2034 + +load ../_helper.bash + +# Exports the variables every scenario needs. +setup_variables() { + export VORTEX_TASK_COPY_DB_ACQUIA_KEY="test-key" + export VORTEX_TASK_COPY_DB_ACQUIA_SECRET="test-secret" + export VORTEX_TASK_COPY_DB_ACQUIA_APP_NAME="testapp" + export VORTEX_TASK_COPY_DB_ACQUIA_SRC="dev" + export VORTEX_TASK_COPY_DB_ACQUIA_DST="test" + export VORTEX_TASK_COPY_DB_ACQUIA_NAME="testdb" + export VORTEX_TASK_COPY_DB_ACQUIA_STATUS_RETRIES="1" + export VORTEX_TASK_COPY_DB_ACQUIA_STATUS_INTERVAL="0" +} + +@test "task-copy-db-acquia: copies the database between environments" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + "[INFO] Started database copying between environments in Acquia." + + "Retrieving authentication token." + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + "[ OK ] Retrieved authentication token." + + "Retrieving testapp application UUID." + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[{"uuid":"app-uuid-123"}]}}' + "[ OK ] Retrieved testapp application UUID." + + "Retrieving source environment ID." + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Ddev # {"_embedded":{"items":[{"id":"src-env-id"}]}}' + "[ OK ] Retrieved source environment ID." + + "Retrieving destination environment ID." + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Dtest # {"_embedded":{"items":[{"id":"dst-env-id"}]}}' + "[ OK ] Retrieved destination environment ID." + + "Copying database from dev to test environment." + '@curl -X POST -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token -H Content-Type: application/json -d {"source":"src-env-id", "name":"testdb"} https://cloud.acquia.com/api/environments/dst-env-id/databases # {"_links":{"notification":{"href":"https://cloud.acquia.com/api/notifications/notif-123"}}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/notifications/notif-123 # {"status":"completed"}' + "[ OK ] Copied database from dev to test environment." + + "[ OK ] Finished database copying between environments in Acquia." + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-db-acquia + steps_run "assert" "${mocks[@]}" + + assert_success + + popd >/dev/null || exit 1 +} + +@test "task-copy-db-acquia: refreshes the token while the task is still running" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[{"uuid":"app-uuid-123"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Ddev # {"_embedded":{"items":[{"id":"src-env-id"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Dtest # {"_embedded":{"items":[{"id":"dst-env-id"}]}}' + '@curl -X POST -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token -H Content-Type: application/json -d {"source":"src-env-id", "name":"testdb"} https://cloud.acquia.com/api/environments/dst-env-id/databases # {"_links":{"notification":{"href":"https://cloud.acquia.com/api/notifications/notif-123"}}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/notifications/notif-123 # {"status":"in-progress"}' + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"refreshed-token"}' + + "Refreshed authentication token." + "[FAIL] Unable to copy database from dev to test environment." + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-db-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + +@test "task-copy-db-acquia: reports the rejected credentials" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + "Retrieving authentication token." + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"error":"invalid_client"}' + "[FAIL] Authentication failed. Check VORTEX_TASK_COPY_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_COPY_DB_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-db-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + +@test "task-copy-db-acquia: reports the missing application" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[]}}' + '[FAIL] Application "testapp" not found. Check application name and access permissions.' + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-db-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + +@test "task-copy-db-acquia: reports the missing source environment" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[{"uuid":"app-uuid-123"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Ddev # {"_embedded":{"items":[]}}' + '[FAIL] Environment "dev" not found in application "testapp". Check environment name.' + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-db-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + +@test "task-copy-db-acquia: missing key" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + mock_curl=$(mock_command "curl") + + setup_variables + unset VORTEX_TASK_COPY_DB_ACQUIA_KEY + unset VORTEX_ACQUIA_KEY + + run .vortex/tooling/src/vortex-task-copy-db-acquia + assert_failure + + assert_output_contains "Missing required value for VORTEX_TASK_COPY_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY." + + # Assert the guard stops the run before the token request. Without this, a key + # reaching the script from any source turns the test into a live, + # authenticated request to Acquia instead of a failed assertion. + assert_equal "0" "$(mock_get_call_num "${mock_curl}")" + + popd >/dev/null || exit 1 +} diff --git a/.vortex/tooling/tests/unit/task-copy-files-acquia.bats b/.vortex/tooling/tests/unit/task-copy-files-acquia.bats new file mode 100644 index 0000000000..c3b3837362 --- /dev/null +++ b/.vortex/tooling/tests/unit/task-copy-files-acquia.bats @@ -0,0 +1,170 @@ +#!/usr/bin/env bats +## +# Unit tests for the 'vortex-task-copy-files-acquia' script. +# +# shellcheck disable=SC2030,SC2031,SC2034 + +load ../_helper.bash + +# Exports the variables every scenario needs. +setup_variables() { + export VORTEX_TASK_COPY_FILES_ACQUIA_KEY="test-key" + export VORTEX_TASK_COPY_FILES_ACQUIA_SECRET="test-secret" + export VORTEX_TASK_COPY_FILES_ACQUIA_APP_NAME="testapp" + export VORTEX_TASK_COPY_FILES_ACQUIA_SRC="dev" + export VORTEX_TASK_COPY_FILES_ACQUIA_DST="test" + export VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_RETRIES="1" + export VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_INTERVAL="0" +} + +@test "task-copy-files-acquia: copies the files between environments" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + "[INFO] Started files copying between environments in Acquia." + + "Retrieving authentication token." + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + "[ OK ] Retrieved authentication token." + + "Retrieving testapp application UUID." + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[{"uuid":"app-uuid-123"}]}}' + "[ OK ] Retrieved testapp application UUID." + + "Retrieving source environment ID." + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Ddev # {"_embedded":{"items":[{"id":"src-env-id"}]}}' + "[ OK ] Retrieved source environment ID." + + "Retrieving destination environment ID." + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Dtest # {"_embedded":{"items":[{"id":"dst-env-id"}]}}' + "[ OK ] Retrieved destination environment ID." + + "Copying files from dev to test environment." + '@curl -X POST -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token -H Content-Type: application/json -d {"source":"src-env-id"} https://cloud.acquia.com/api/environments/dst-env-id/files # {"_links":{"notification":{"href":"https://cloud.acquia.com/api/notifications/notif-123"}}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/notifications/notif-123 # {"status":"completed"}' + "[ OK ] Copied files from dev to test environment." + + "[ OK ] Finished files copying between environments in Acquia." + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-files-acquia + steps_run "assert" "${mocks[@]}" + + assert_success + + popd >/dev/null || exit 1 +} + +@test "task-copy-files-acquia: refreshes the token while the task is still running" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[{"uuid":"app-uuid-123"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Ddev # {"_embedded":{"items":[{"id":"src-env-id"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Dtest # {"_embedded":{"items":[{"id":"dst-env-id"}]}}' + '@curl -X POST -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token -H Content-Type: application/json -d {"source":"src-env-id"} https://cloud.acquia.com/api/environments/dst-env-id/files # {"_links":{"notification":{"href":"https://cloud.acquia.com/api/notifications/notif-123"}}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/notifications/notif-123 # {"status":"in-progress"}' + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"refreshed-token"}' + + "Refreshed authentication token." + "[FAIL] Unable to copy files from dev to test environment." + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-files-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + +@test "task-copy-files-acquia: reports the rejected credentials" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + "Retrieving authentication token." + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"error":"invalid_client"}' + "[FAIL] Authentication failed. Check VORTEX_TASK_COPY_FILES_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_COPY_FILES_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-files-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + +@test "task-copy-files-acquia: reports the missing application" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[]}}' + '[FAIL] Application "testapp" not found. Check application name and access permissions.' + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-files-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + +@test "task-copy-files-acquia: reports the missing destination environment" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[{"uuid":"app-uuid-123"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Ddev # {"_embedded":{"items":[{"id":"src-env-id"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Dtest # {"_embedded":{"items":[]}}' + '[FAIL] Environment "test" not found in application "testapp". Check environment name.' + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-files-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + +@test "task-copy-files-acquia: missing key" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + mock_curl=$(mock_command "curl") + + setup_variables + unset VORTEX_TASK_COPY_FILES_ACQUIA_KEY + unset VORTEX_ACQUIA_KEY + + run .vortex/tooling/src/vortex-task-copy-files-acquia + assert_failure + + assert_output_contains "Missing required value for VORTEX_TASK_COPY_FILES_ACQUIA_KEY or VORTEX_ACQUIA_KEY." + + # Assert the guard stops the run before the token request. Without this, a key + # reaching the script from any source turns the test into a live, + # authenticated request to Acquia instead of a failed assertion. + assert_equal "0" "$(mock_get_call_num "${mock_curl}")" + + popd >/dev/null || exit 1 +} diff --git a/.vortex/tooling/tests/unit/task-custom-lagoon.bats b/.vortex/tooling/tests/unit/task-custom-lagoon.bats new file mode 100644 index 0000000000..8a77494f7e --- /dev/null +++ b/.vortex/tooling/tests/unit/task-custom-lagoon.bats @@ -0,0 +1,149 @@ +#!/usr/bin/env bats +## +# Unit tests for the 'vortex-task-custom-lagoon' script. +# +# shellcheck disable=SC2030,SC2031,SC2034 + +load ../_helper.bash + +@test "task-custom-lagoon: runs the task against the configured instance" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + fixture_ssh_key_prepare + fixture_ssh_key + + export LAGOON_PROJECT="test_project" + export VORTEX_TASK_CUSTOM_LAGOON_NAME="Test task" + export VORTEX_TASK_CUSTOM_LAGOON_BRANCH="test-branch" + export VORTEX_TASK_CUSTOM_LAGOON_COMMAND="drush status" + + declare -a STEPS=( + "[INFO] Started Lagoon task Test task." + "@ssh-add -l # ${HOME}/.ssh/id_rsa" + + "Configuring Lagoon instance." + "@lagoon config add --force --lagoon amazeeio --graphql https://api.lagoon.amazeeio.cloud/graphql --hostname ssh.lagoon.amazeeio.cloud --port 32222" + "[ OK ] Configured Lagoon instance." + + "Creating Test task task: project: test_project, branch: test-branch." + "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project run custom --environment test-branch --name Test task --command drush status" + "[ OK ] Created Test task task: project: test_project, branch: test-branch." + + "[ OK ] Finished Lagoon task Test task." + + "- Installing Lagoon CLI." + ) + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-custom-lagoon + steps_run "assert" "${mocks[@]}" + + assert_success + + popd >/dev/null || exit 1 +} + +@test "task-custom-lagoon: installs the CLI when the installation is forced" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + fixture_ssh_key_prepare + fixture_ssh_key + + export LAGOON_PROJECT="test_project" + export VORTEX_TASK_CUSTOM_LAGOON_NAME="Test task" + export VORTEX_TASK_CUSTOM_LAGOON_BRANCH="test-branch" + export VORTEX_TASK_CUSTOM_LAGOON_COMMAND="drush status" + export VORTEX_TASK_CUSTOM_LAGOON_CLI_FORCE_INSTALL="1" + export VORTEX_TASK_CUSTOM_LAGOON_CLI_PATH="${BUILD_DIR}" + export VORTEX_TASK_CUSTOM_LAGOON_CLI_VERSION="v0.32.0" + + # The release asset name carries the running platform and architecture, so + # the expected URL is resolved the same way the script resolves it. + platform=$(uname -s | tr '[:upper:]' '[:lower:]') + arch_suffix=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') + download_url="https://github.com/uselagoon/lagoon-cli/releases/download/v0.32.0/lagoon-cli-v0.32.0-${platform}-${arch_suffix}" + + declare -a STEPS=( + "Installing Lagoon CLI." + "@ssh-add -l # ${HOME}/.ssh/id_rsa" + + "Downloading Lagoon CLI from ${download_url}." + "@curl -fSLs -o ${BUILD_DIR}/lagoon ${download_url} # 0 # # touch ${BUILD_DIR}/lagoon" + "Installing Lagoon CLI to ${BUILD_DIR}/lagoon." + "[ OK ] Installed Lagoon CLI." + + "@lagoon config add --force --lagoon amazeeio --graphql https://api.lagoon.amazeeio.cloud/graphql --hostname ssh.lagoon.amazeeio.cloud --port 32222" + "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project run custom --environment test-branch --name Test task --command drush status" + + "[ OK ] Finished Lagoon task Test task." + ) + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-custom-lagoon + steps_run "assert" "${mocks[@]}" + + assert_success + + popd >/dev/null || exit 1 +} + +@test "task-custom-lagoon: fails when the project is missing" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + mock_lagoon=$(mock_command "lagoon") + + # The shipped '.env' carries a project name, and the script's own loader + # restores the exported environment over it, so both variables are emptied. + export LAGOON_PROJECT="" + export VORTEX_TASK_CUSTOM_LAGOON_PROJECT="" + export VORTEX_TASK_CUSTOM_LAGOON_BRANCH="test-branch" + export VORTEX_TASK_CUSTOM_LAGOON_COMMAND="drush status" + + run .vortex/tooling/src/vortex-task-custom-lagoon + assert_failure + + assert_output_contains "Missing required value for VORTEX_TASK_CUSTOM_LAGOON_PROJECT." + + # Assert the guard stops the run before the CLI is reached. Without this, a + # project name reaching the script from any source turns the test into a + # live, authenticated request to Lagoon instead of a failed assertion. + assert_equal "0" "$(mock_get_call_num "${mock_lagoon}")" + + popd >/dev/null || exit 1 +} + +@test "task-custom-lagoon: fails when the branch is missing" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + mock_lagoon=$(mock_command "lagoon") + + export LAGOON_PROJECT="test_project" + unset VORTEX_TASK_CUSTOM_LAGOON_BRANCH + export VORTEX_TASK_CUSTOM_LAGOON_COMMAND="drush status" + + run .vortex/tooling/src/vortex-task-custom-lagoon + assert_failure + + assert_output_contains "Missing required value for VORTEX_TASK_CUSTOM_LAGOON_BRANCH." + assert_equal "0" "$(mock_get_call_num "${mock_lagoon}")" + + popd >/dev/null || exit 1 +} + +@test "task-custom-lagoon: fails when the command is missing" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + mock_lagoon=$(mock_command "lagoon") + + export LAGOON_PROJECT="test_project" + export VORTEX_TASK_CUSTOM_LAGOON_BRANCH="test-branch" + unset VORTEX_TASK_CUSTOM_LAGOON_COMMAND + + run .vortex/tooling/src/vortex-task-custom-lagoon + assert_failure + + assert_output_contains "Missing required value for VORTEX_TASK_CUSTOM_LAGOON_COMMAND." + assert_equal "0" "$(mock_get_call_num "${mock_lagoon}")" + + popd >/dev/null || exit 1 +} From 6bd70ace964b37eddf8add7a9186e0deb202d9f5 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 9 Sep 2026 10:40:17 +1000 Subject: [PATCH 08/11] [#3113] Moved the override flag rationale into the function docblock. --- .vortex/tooling/src/vortex-deploy-lagoon | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.vortex/tooling/src/vortex-deploy-lagoon b/.vortex/tooling/src/vortex-deploy-lagoon index b1157ca509..aefb189d36 100755 --- a/.vortex/tooling/src/vortex-deploy-lagoon +++ b/.vortex/tooling/src/vortex-deploy-lagoon @@ -139,13 +139,13 @@ close_deploy() { } # Read the database import override flag on an environment into -# ${override_db_state}, ${override_db_original} and ${override_db_scope}. +# ${override_db_state}, ${override_db_original} and ${override_db_scope}. The +# flag is set for a single build and then restored to its previous state, so an +# environment that had no flag is not left with one. discover_override_db() { local environment="${1}" local override_db_vars override_db_count - # The flag is set for a single build and then restored to its previous - # state, so an environment that had no flag is not left with one. task "Discovering a database import override flag." override_db_state="unknown" override_db_original="" From e0f2cec80ef544edf4c3330cb805884b7e3cc1e7 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 9 Sep 2026 10:47:30 +1000 Subject: [PATCH 09/11] [#3113] Covered both missing environment branches in the Acquia copy tasks. --- .../tests/unit/task-copy-db-acquia.bats | 22 +++++++++++++++++++ .../tests/unit/task-copy-files-acquia.bats | 21 ++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/.vortex/tooling/tests/unit/task-copy-db-acquia.bats b/.vortex/tooling/tests/unit/task-copy-db-acquia.bats index 8ad0e0bf81..b3bb64aa1b 100644 --- a/.vortex/tooling/tests/unit/task-copy-db-acquia.bats +++ b/.vortex/tooling/tests/unit/task-copy-db-acquia.bats @@ -147,6 +147,28 @@ setup_variables() { popd >/dev/null || exit 1 } +@test "task-copy-db-acquia: reports the missing destination environment" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[{"uuid":"app-uuid-123"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Ddev # {"_embedded":{"items":[{"id":"src-env-id"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Dtest # {"_embedded":{"items":[]}}' + '[FAIL] Environment "test" not found in application "testapp". Check environment name.' + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-db-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + @test "task-copy-db-acquia: missing key" { pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 diff --git a/.vortex/tooling/tests/unit/task-copy-files-acquia.bats b/.vortex/tooling/tests/unit/task-copy-files-acquia.bats index c3b3837362..3d43afb528 100644 --- a/.vortex/tooling/tests/unit/task-copy-files-acquia.bats +++ b/.vortex/tooling/tests/unit/task-copy-files-acquia.bats @@ -125,6 +125,27 @@ setup_variables() { popd >/dev/null || exit 1 } +@test "task-copy-files-acquia: reports the missing source environment" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[{"uuid":"app-uuid-123"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Ddev # {"_embedded":{"items":[]}}' + '[FAIL] Environment "dev" not found in application "testapp". Check environment name.' + ) + + setup_variables + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-files-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + @test "task-copy-files-acquia: reports the missing destination environment" { pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 From 5cc5d15d967ac85cebe3c69c879de4ea67e24667 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 9 Sep 2026 11:39:31 +1000 Subject: [PATCH 10/11] Addressed code review: strengthened the Acquia task test assertions and coverage. --- .../tests/unit/task-copy-db-acquia.bats | 33 ++++++++- .../tests/unit/task-copy-files-acquia.bats | 33 ++++++++- .../tests/unit/task-purge-cache-acquia.bats | 70 +++++++++++++++++++ 3 files changed, 132 insertions(+), 4 deletions(-) diff --git a/.vortex/tooling/tests/unit/task-copy-db-acquia.bats b/.vortex/tooling/tests/unit/task-copy-db-acquia.bats index b3bb64aa1b..70d383161f 100644 --- a/.vortex/tooling/tests/unit/task-copy-db-acquia.bats +++ b/.vortex/tooling/tests/unit/task-copy-db-acquia.bats @@ -59,7 +59,7 @@ setup_variables() { popd >/dev/null || exit 1 } -@test "task-copy-db-acquia: refreshes the token while the task is still running" { +@test "task-copy-db-acquia: polls with the refreshed token after the first poll" { pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 declare -a STEPS=( @@ -70,8 +70,37 @@ setup_variables() { '@curl -X POST -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token -H Content-Type: application/json -d {"source":"src-env-id", "name":"testdb"} https://cloud.acquia.com/api/environments/dst-env-id/databases # {"_links":{"notification":{"href":"https://cloud.acquia.com/api/notifications/notif-123"}}}' '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/notifications/notif-123 # {"status":"in-progress"}' '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"refreshed-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer refreshed-token https://cloud.acquia.com/api/notifications/notif-123 # {"status":"completed"}' "Refreshed authentication token." + "[ OK ] Copied database from dev to test environment." + "[ OK ] Finished database copying between environments in Acquia." + ) + + setup_variables + export VORTEX_TASK_COPY_DB_ACQUIA_STATUS_RETRIES="2" + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-db-acquia + steps_run "assert" "${mocks[@]}" + + assert_success + + popd >/dev/null || exit 1 +} + +@test "task-copy-db-acquia: fails when the task never completes" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[{"uuid":"app-uuid-123"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Ddev # {"_embedded":{"items":[{"id":"src-env-id"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Dtest # {"_embedded":{"items":[{"id":"dst-env-id"}]}}' + '@curl -X POST -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token -H Content-Type: application/json -d {"source":"src-env-id", "name":"testdb"} https://cloud.acquia.com/api/environments/dst-env-id/databases # {"_links":{"notification":{"href":"https://cloud.acquia.com/api/notifications/notif-123"}}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/notifications/notif-123 # {"status":"in-progress"}' + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"refreshed-token"}' + "[FAIL] Unable to copy database from dev to test environment." ) @@ -92,7 +121,7 @@ setup_variables() { declare -a STEPS=( "Retrieving authentication token." '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"error":"invalid_client"}' - "[FAIL] Authentication failed. Check VORTEX_TASK_COPY_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_COPY_DB_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." + '[FAIL] Authentication failed. Check VORTEX_TASK_COPY_DB_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_COPY_DB_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET. API response: {"error":"invalid_client"}' ) setup_variables diff --git a/.vortex/tooling/tests/unit/task-copy-files-acquia.bats b/.vortex/tooling/tests/unit/task-copy-files-acquia.bats index 3d43afb528..babcef7e3d 100644 --- a/.vortex/tooling/tests/unit/task-copy-files-acquia.bats +++ b/.vortex/tooling/tests/unit/task-copy-files-acquia.bats @@ -58,7 +58,7 @@ setup_variables() { popd >/dev/null || exit 1 } -@test "task-copy-files-acquia: refreshes the token while the task is still running" { +@test "task-copy-files-acquia: polls with the refreshed token after the first poll" { pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 declare -a STEPS=( @@ -69,8 +69,37 @@ setup_variables() { '@curl -X POST -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token -H Content-Type: application/json -d {"source":"src-env-id"} https://cloud.acquia.com/api/environments/dst-env-id/files # {"_links":{"notification":{"href":"https://cloud.acquia.com/api/notifications/notif-123"}}}' '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/notifications/notif-123 # {"status":"in-progress"}' '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"refreshed-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer refreshed-token https://cloud.acquia.com/api/notifications/notif-123 # {"status":"completed"}' "Refreshed authentication token." + "[ OK ] Copied files from dev to test environment." + "[ OK ] Finished files copying between environments in Acquia." + ) + + setup_variables + export VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_RETRIES="2" + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-copy-files-acquia + steps_run "assert" "${mocks[@]}" + + assert_success + + popd >/dev/null || exit 1 +} + +@test "task-copy-files-acquia: fails when the task never completes" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[{"uuid":"app-uuid-123"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Ddev # {"_embedded":{"items":[{"id":"src-env-id"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Dtest # {"_embedded":{"items":[{"id":"dst-env-id"}]}}' + '@curl -X POST -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token -H Content-Type: application/json -d {"source":"src-env-id"} https://cloud.acquia.com/api/environments/dst-env-id/files # {"_links":{"notification":{"href":"https://cloud.acquia.com/api/notifications/notif-123"}}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/notifications/notif-123 # {"status":"in-progress"}' + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"refreshed-token"}' + "[FAIL] Unable to copy files from dev to test environment." ) @@ -91,7 +120,7 @@ setup_variables() { declare -a STEPS=( "Retrieving authentication token." '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"error":"invalid_client"}' - "[FAIL] Authentication failed. Check VORTEX_TASK_COPY_FILES_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_COPY_FILES_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET." + '[FAIL] Authentication failed. Check VORTEX_TASK_COPY_FILES_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_COPY_FILES_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET. API response: {"error":"invalid_client"}' ) setup_variables diff --git a/.vortex/tooling/tests/unit/task-purge-cache-acquia.bats b/.vortex/tooling/tests/unit/task-purge-cache-acquia.bats index 57eb29ea89..16671d5791 100644 --- a/.vortex/tooling/tests/unit/task-purge-cache-acquia.bats +++ b/.vortex/tooling/tests/unit/task-purge-cache-acquia.bats @@ -213,3 +213,73 @@ assert_purge_domain() { popd >/dev/null || exit 1 } + +@test "task-purge-cache-acquia: reports the rejected credentials" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + "Retrieving authentication token." + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"error":"invalid_client"}' + '[FAIL] Authentication failed. Check VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY or VORTEX_ACQUIA_KEY and VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET or VORTEX_ACQUIA_SECRET. API response: {"error":"invalid_client"}' + ) + + export VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY="test-key" + export VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET="test-secret" + export VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME="testapp" + export VORTEX_TASK_PURGE_CACHE_ACQUIA_ENV="prod" + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-purge-cache-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + +@test "task-purge-cache-acquia: reports the missing application" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[]}}' + '[FAIL] Application "testapp" not found. Check application name and access permissions.' + ) + + export VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY="test-key" + export VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET="test-secret" + export VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME="testapp" + export VORTEX_TASK_PURGE_CACHE_ACQUIA_ENV="prod" + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-purge-cache-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} + +@test "task-purge-cache-acquia: reports the missing environment" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + declare -a STEPS=( + '@curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode client_id=test-key --data-urlencode client_secret=test-secret --data-urlencode grant_type=client_credentials # {"access_token":"test-token"}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications?filter=name%3Dtestapp # {"_embedded":{"items":[{"uuid":"app-uuid-123"}]}}' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/applications/app-uuid-123/environments?filter=name%3Dprod # {"_embedded":{"items":[]}}' + '[FAIL] Environment "prod" not found in application "testapp". Check environment name.' + ) + + export VORTEX_TASK_PURGE_CACHE_ACQUIA_KEY="test-key" + export VORTEX_TASK_PURGE_CACHE_ACQUIA_SECRET="test-secret" + export VORTEX_TASK_PURGE_CACHE_ACQUIA_APP_NAME="testapp" + export VORTEX_TASK_PURGE_CACHE_ACQUIA_ENV="prod" + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-task-purge-cache-acquia + steps_run "assert" "${mocks[@]}" + + assert_failure + + popd >/dev/null || exit 1 +} From d858736a2eddfc78a7bb177f0975426944bf5ad7 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 9 Sep 2026 12:59:04 +1000 Subject: [PATCH 11/11] [#3113] Reverted the Lagoon deploy extraction and the boilerplate helper change. --- .../maintenance/script-boilerplate.sh | 4 +- .vortex/tooling/src/vortex-deploy-lagoon | 191 ++++++++++-------- 2 files changed, 110 insertions(+), 85 deletions(-) diff --git a/.vortex/docs/content/contributing/maintenance/script-boilerplate.sh b/.vortex/docs/content/contributing/maintenance/script-boilerplate.sh index 1855cd6a4e..e3e3ff2704 100755 --- a/.vortex/docs/content/contributing/maintenance/script-boilerplate.sh +++ b/.vortex/docs/content/contributing/maintenance/script-boilerplate.sh @@ -17,8 +17,8 @@ VORTEX_EXAMPLE_URL="${VORTEX_EXAMPLE_URL:-http://example.com}" # @formatter:off info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[36m[INFO] %s\033[0m\n" "${1}" || printf "[INFO] %s\n" "${1}"; } note() { printf " %s\n" "${1}"; } -task() { _TASK_START=$(date +%s); [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[TASK] %s\033[0m\n" "${1}" || printf "[TASK] %s\n" "${1}"; } -pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START))s)" && unset _TASK_START; [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s%s\033[0m\n" "${1}" "${_d}" || printf "[ OK ] %s%s\n" "${1}" "${_d}"; } +task() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[TASK] %s\033[0m\n" "${1}" || printf "[TASK] %s\n" "${1}"; } +pass() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s\033[0m\n" "${1}" || printf "[ OK ] %s\n" "${1}"; } fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; exit "${2:-1}"; } # @formatter:on diff --git a/.vortex/tooling/src/vortex-deploy-lagoon b/.vortex/tooling/src/vortex-deploy-lagoon index aefb189d36..06e99bb025 100755 --- a/.vortex/tooling/src/vortex-deploy-lagoon +++ b/.vortex/tooling/src/vortex-deploy-lagoon @@ -138,89 +138,12 @@ close_deploy() { fail "Failed to request ${1}" "${exit_code}" } -# Read the database import override flag on an environment into -# ${override_db_state}, ${override_db_original} and ${override_db_scope}. The -# flag is set for a single build and then restored to its previous state, so an -# environment that had no flag is not left with one. -discover_override_db() { - local environment="${1}" - local override_db_vars override_db_count - - task "Discovering a database import override flag." - override_db_state="unknown" - override_db_original="" - override_db_scope="global" - - if override_db_vars="$(lagoon list variables --environment "${environment}" --reveal --output-json 2>/dev/null)"; then - if override_db_count="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB")] | length' 2>/dev/null)"; then - if [ "${override_db_count}" = "0" ]; then - override_db_state="absent" - else - override_db_state="present" - override_db_original="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .value] | first // ""')" - override_db_scope="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .scope] | first // "global"')" - fi - fi - fi - - if [ "${override_db_state}" = "unknown" ]; then - # An unreadable variable list is not an absent flag: adding one and - # deleting it afterwards would destroy a flag that already exists, so - # the flag is left alone. - note "WARNING: Could not read environment variables. A database import override flag was left unchanged and may not match the requested deployment action." - elif [ "${override_db_state}" = "absent" ]; then - note "No existing database import override flag found." - else - note "Found an existing database import override flag with value \"${override_db_original}\" and scope \"${override_db_scope}\"." - fi - - pass "Completed database import override flag discovery." -} - -# Write the flag this deployment needs onto an environment. A flag that was not -# there is added, one that was there is updated in its own scope. -apply_override_db() { - local environment="${1}" - local value="${2}" - - if [ "${override_db_state}" = "absent" ]; then - task "Adding a database import override flag with value ${value}." - lagoon add variable --environment "${environment}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${value}" --scope global || true - pass "Added a database import override flag with value ${value}." - elif [ "${override_db_state}" = "present" ]; then - task "Updating a database import override flag to ${value}." - lagoon update variable --environment "${environment}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${value}" --scope "${override_db_scope}" || true - pass "Updated a database import override flag to ${value}." - fi -} - -# Return the flag to the state discovery found it in. -restore_override_db() { - local environment="${1}" - - if [ "${override_db_state}" = "absent" ]; then - lagoon delete variable --environment "${environment}" --name VORTEX_PROVISION_OVERRIDE_DB || true - note "Removed a database import override flag." - elif [ "${override_db_state}" = "present" ]; then - lagoon update variable --environment "${environment}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_original}" --scope "${override_db_scope}" || true - note "Restored a database import override flag to ${override_db_original}." - fi -} - # Deploy status, carried from run_lagoon_deploy to close_deploy. exit_code=0 # Raw CLI output of a failed deploy, printed with the failure line. deploy_error="" -# The database import override flag value this deployment needs while Lagoon -# queues the build. -if [ "${VORTEX_DEPLOY_LAGOON_ACTION}" = "deploy_override_db" ]; then - override_db_value=1 -else - override_db_value=0 -fi - info "Started Lagoon deployment." if [ "${VORTEX_DEPLOY_MODE}" = "tag" ]; then @@ -288,8 +211,53 @@ else pass "Completed environment discovery." if [ "${is_redeploy:-}" = "1" ]; then - discover_override_db "${deploy_pr_full}" - apply_override_db "${deploy_pr_full}" "${override_db_value}" + # The flag value this deployment needs while Lagoon queues the build. + if [ "${VORTEX_DEPLOY_LAGOON_ACTION}" = "deploy_override_db" ]; then + override_db_value=1 + else + override_db_value=0 + fi + + # The flag is set for a single build and then restored to its previous + # state, so an environment that had no flag is not left with one. + task "Discovering a database import override flag." + override_db_state="unknown" + override_db_original="" + override_db_scope="global" + + if override_db_vars="$(lagoon list variables --environment "${deploy_pr_full}" --reveal --output-json 2>/dev/null)"; then + if override_db_count="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB")] | length' 2>/dev/null)"; then + if [ "${override_db_count}" = "0" ]; then + override_db_state="absent" + else + override_db_state="present" + override_db_original="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .value] | first // ""')" + override_db_scope="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .scope] | first // "global"')" + fi + fi + fi + + if [ "${override_db_state}" = "unknown" ]; then + # An unreadable variable list is not an absent flag: adding one and + # deleting it afterwards would destroy a flag that already exists, so + # the flag is left alone. + note "WARNING: Could not read environment variables. A database import override flag was left unchanged and may not match the requested deployment action." + elif [ "${override_db_state}" = "absent" ]; then + note "No existing database import override flag found." + else + note "Found an existing database import override flag with value \"${override_db_original}\" and scope \"${override_db_scope}\"." + fi + pass "Completed database import override flag discovery." + + if [ "${override_db_state}" = "absent" ]; then + task "Adding a database import override flag with value ${override_db_value}." + lagoon add variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_value}" --scope global || true + pass "Added a database import override flag with value ${override_db_value}." + elif [ "${override_db_state}" = "present" ]; then + task "Updating a database import override flag to ${override_db_value}." + lagoon update variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_value}" --scope "${override_db_scope}" || true + pass "Updated a database import override flag to ${override_db_value}." + fi task "Redeploying environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, PR: ${VORTEX_DEPLOY_LAGOON_PR}." run_lagoon_deploy deploy pullrequest --number "${VORTEX_DEPLOY_LAGOON_PR}" --base-branch-name "${VORTEX_DEPLOY_LAGOON_PR_BASE_BRANCH}" --base-branch-ref "origin/${VORTEX_DEPLOY_LAGOON_PR_BASE_BRANCH}" --head-branch-name "${VORTEX_DEPLOY_LAGOON_BRANCH}" --head-branch-ref "${VORTEX_DEPLOY_LAGOON_PR_HEAD}" --title "${deploy_pr_full}" @@ -300,7 +268,13 @@ else note "Waiting for deployment to be queued." sleep 10 - restore_override_db "${deploy_pr_full}" + if [ "${override_db_state}" = "absent" ]; then + lagoon delete variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB || true + note "Removed a database import override flag." + elif [ "${override_db_state}" = "present" ]; then + lagoon update variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_original}" --scope "${override_db_scope}" || true + note "Restored a database import override flag to ${override_db_original}." + fi close_deploy "redeployment of environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, PR: ${VORTEX_DEPLOY_LAGOON_PR}." @@ -332,8 +306,53 @@ else pass "Completed environment discovery." if [ "${is_redeploy:-}" = "1" ]; then - discover_override_db "${VORTEX_DEPLOY_LAGOON_BRANCH}" - apply_override_db "${VORTEX_DEPLOY_LAGOON_BRANCH}" "${override_db_value}" + # The flag value this deployment needs while Lagoon queues the build. + if [ "${VORTEX_DEPLOY_LAGOON_ACTION}" = "deploy_override_db" ]; then + override_db_value=1 + else + override_db_value=0 + fi + + # The flag is set for a single build and then restored to its previous + # state, so an environment that had no flag is not left with one. + task "Discovering a database import override flag." + override_db_state="unknown" + override_db_original="" + override_db_scope="global" + + if override_db_vars="$(lagoon list variables --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --reveal --output-json 2>/dev/null)"; then + if override_db_count="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB")] | length' 2>/dev/null)"; then + if [ "${override_db_count}" = "0" ]; then + override_db_state="absent" + else + override_db_state="present" + override_db_original="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .value] | first // ""')" + override_db_scope="$(printf '%s' "${override_db_vars}" | jq -r '[.data[]? | select(type == "object") | select(.name? == "VORTEX_PROVISION_OVERRIDE_DB") | .scope] | first // "global"')" + fi + fi + fi + + if [ "${override_db_state}" = "unknown" ]; then + # An unreadable variable list is not an absent flag: adding one and + # deleting it afterwards would destroy a flag that already exists, so + # the flag is left alone. + note "WARNING: Could not read environment variables. A database import override flag was left unchanged and may not match the requested deployment action." + elif [ "${override_db_state}" = "absent" ]; then + note "No existing database import override flag found." + else + note "Found an existing database import override flag with value \"${override_db_original}\" and scope \"${override_db_scope}\"." + fi + pass "Completed database import override flag discovery." + + if [ "${override_db_state}" = "absent" ]; then + task "Adding a database import override flag with value ${override_db_value}." + lagoon add variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_value}" --scope global || true + pass "Added a database import override flag with value ${override_db_value}." + elif [ "${override_db_state}" = "present" ]; then + task "Updating a database import override flag to ${override_db_value}." + lagoon update variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_value}" --scope "${override_db_scope}" || true + pass "Updated a database import override flag to ${override_db_value}." + fi task "Redeploying environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}." run_lagoon_deploy deploy latest --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" @@ -344,7 +363,13 @@ else note "Waiting for deployment to be queued." sleep 10 - restore_override_db "${VORTEX_DEPLOY_LAGOON_BRANCH}" + if [ "${override_db_state}" = "absent" ]; then + lagoon delete variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB || true + note "Removed a database import override flag." + elif [ "${override_db_state}" = "present" ]; then + lagoon update variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_original}" --scope "${override_db_scope}" || true + note "Restored a database import override flag to ${override_db_original}." + fi close_deploy "redeployment of environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}."