diff --git a/.vortex/tooling/src/vortex-export-db-file b/.vortex/tooling/src/vortex-export-db-file index 489190440..cf1d5936d 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-fetch-db-acquia b/.vortex/tooling/src/vortex-fetch-db-acquia index 03730e331..18f862be1 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." @@ -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-fetch-db-s3 b/.vortex/tooling/src/vortex-fetch-db-s3 index b7703a5a5..d45d027c6 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-import-db-file b/.vortex/tooling/src/vortex-import-db-file index ef562ceeb..d992e50d3 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-notify-diffy b/.vortex/tooling/src/vortex-notify-diffy index be1a3d2d9..0dff63c13 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-notify-github b/.vortex/tooling/src/vortex-notify-github index 237a3c83b..e83385592 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 05df2c0cd..1c2340a16 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-provision-sanitize-db b/.vortex/tooling/src/vortex-provision-sanitize-db index c6e8ef4ea..2024020fe 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." diff --git a/.vortex/tooling/src/vortex-push-db-s3 b/.vortex/tooling/src/vortex-push-db-s3 index 3bead90ef..93470ad6e 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 49dc8a505..302f6213c 100755 --- a/.vortex/tooling/src/vortex-task-copy-db-acquia +++ b/.vortex/tooling/src/vortex-task-copy-db-acquia @@ -53,19 +53,19 @@ 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);" } -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 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." @@ -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 99e45f402..baba63bcd 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." @@ -74,26 +74,54 @@ extract_json_value() { 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-custom-lagoon b/.vortex/tooling/src/vortex-task-custom-lagoon index a780662e8..2f0a584d8 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}." diff --git a/.vortex/tooling/src/vortex-task-purge-cache-acquia b/.vortex/tooling/src/vortex-task-purge-cache-acquia index 6e7b3020b..5d5c941f4 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." @@ -74,20 +74,41 @@ extract_json_value() { 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 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 000000000..70d383161 --- /dev/null +++ b/.vortex/tooling/tests/unit/task-copy-db-acquia.bats @@ -0,0 +1,221 @@ +#!/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: polls with the refreshed token after the first poll" { + 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"}' + '@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." + ) + + 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. API response: {"error":"invalid_client"}' + ) + + 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: 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 + + 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 000000000..babcef7e3 --- /dev/null +++ b/.vortex/tooling/tests/unit/task-copy-files-acquia.bats @@ -0,0 +1,220 @@ +#!/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: polls with the refreshed token after the first poll" { + 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"}' + '@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." + ) + + 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. API response: {"error":"invalid_client"}' + ) + + 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 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 + + 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 000000000..8a77494f7 --- /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 +} diff --git a/.vortex/tooling/tests/unit/task-purge-cache-acquia.bats b/.vortex/tooling/tests/unit/task-purge-cache-acquia.bats index 57eb29ea8..16671d579 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 +}