diff --git a/.vortex/CLAUDE.md b/.vortex/CLAUDE.md index fe8363d5e..5426e197b 100644 --- a/.vortex/CLAUDE.md +++ b/.vortex/CLAUDE.md @@ -73,15 +73,13 @@ The boilerplate leaves out two things the repository's scripts carry: `scripts/provision-30-search-index.sh`; converting them to the boilerplate form changes stdout that the tests assert on. -**Output helpers** - every `task` MUST be closed by a `pass` or a `fail`. A -task announces work that is starting, so it always reports its outcome; a `task` -with no closing line leaves the reader unable to tell whether the step -succeeded. This holds even when the work itself cannot fail (e.g. a command -suffixed with `|| true`) - close it with `pass`. - -Use the other helpers for what they are: `info` for the banners that open and -close an operation, `note` for a standalone remark that starts no task, and -`fail` to abort. +**Output helpers** - every `task` MUST be closed by exactly one `pass` or `fail`, on every path through the script. A task announces work that is starting, so it always reports its outcome; a `task` with no closing line leaves the reader unable to tell whether the step succeeded. This holds even when the work itself cannot fail (e.g. a command suffixed with `|| true`) - close it with `pass`. + +A `task` MUST NOT open while another is open. A step that runs inside an open task is a `note`, and a loop body that repeats work under one task reports each iteration with `note`. + +The rule is one-directional: a closing line does not need an opening `task`. A check reports `pass` when it holds and `fail` when it does not, and an operation that is skipped or finished reports `pass` on its own. + +Use the other helpers for what they are: `info` for the banners that open and close an operation, `note` for a standalone remark that starts no task, and `fail` to abort. ```bash task "Disabling Search API Solr server." diff --git a/.vortex/docs/content/contributing/maintenance/template.mdx b/.vortex/docs/content/contributing/maintenance/template.mdx index 4ed949528..196b8aa98 100644 --- a/.vortex/docs/content/contributing/maintenance/template.mdx +++ b/.vortex/docs/content/contributing/maintenance/template.mdx @@ -97,21 +97,36 @@ Please refer to [RFC2119](https://www.ietf.org/rfc/rfc2119.txt) for meaning of w pass "Finished GitHub notification for operation ${VORTEX_NOTIFY_EVENT}." ``` -11. MUST use uppercase global variables. -12. MUST use lowercase local variables. -13. MUST use long options instead of short options for readability. I.e., `drush cache:rebuild` instead of `drush cr`. -14. MUST use `VORTEX_` prefix for variables, unless it is a known 3-rd party +11. MUST close every `task` message with exactly one `pass` or `fail` message, + on every path through the script, e.g.: + + ```shell + task "Disabling Search API Solr server." + drush search-api:server-disable solr || true + pass "Disabled Search API Solr server." + ``` + +12. MUST NOT open a `task` message while another one is open. A step that runs + inside an open task, including each iteration of a loop, uses a `note` + message. +13. MAY use a `pass` or a `fail` message on its own. A check reports `pass` when + it holds and `fail` when it does not, and an operation that is skipped or + finished reports `pass` without announcing a `task` first. +14. MUST use uppercase global variables. +15. MUST use lowercase local variables. +16. MUST use long options instead of short options for readability. I.e., `drush cache:rebuild` instead of `drush cr`. +17. MUST use `VORTEX_` prefix for variables, unless it is a known 3-rd party variable like `PACKAGE_TOKEN` or `COMPOSER`. -15. MUST use a script-specific prefix. I.e., for `vortex-notify`, the variable +18. MUST use a script-specific prefix. I.e., for `vortex-notify`, the variable to skip notifications should start with `VORTEX_NOTIFY_`. -16. MAY rely on variables from the external scripts (not prefixed with a +19. MAY rely on variables from the external scripts (not prefixed with a script-specific prefix), but MUST declare such variables in the header of the file. -17. MAY call other **Vortex** scripts (discouraged), but MUST source them rather - than creating a sub-process. This is to allow passing environment variables - down the call stack. -18. SHOULD use `note` messages for informing about the script progress. -19. MUST use variables in the form of `${VAR}`. +20. MAY call other **Vortex** scripts (discouraged), but MUST source them rather + than creating a sub-process. Sourcing runs the called script in the current + shell, so both scripts share the same variables. +21. SHOULD use `note` messages for informing about the script progress. +22. MUST use variables in the form of `${VAR}`. ### Variables diff --git a/.vortex/tests/phpunit/Functional/DeploymentTest.php b/.vortex/tests/phpunit/Functional/DeploymentTest.php index 09191cf9c..a5fd019a4 100644 --- a/.vortex/tests/phpunit/Functional/DeploymentTest.php +++ b/.vortex/tests/phpunit/Functional/DeploymentTest.php @@ -243,6 +243,7 @@ public function testDeploymentArtifact(): void { '* Copying git repo files meta file to the deploy code repo.', '* Copying deployment .gitignore as it may not exist in deploy code source files.', '* Running artifact builder.', + '* Ran artifact builder.', '* Finished artifact deployment.', ], txt: 'Artifact deployment should complete successfully', env: [ 'VORTEX_DEPLOY_TYPES' => 'artifact', diff --git a/.vortex/tooling/src/vortex-deploy-artifact b/.vortex/tooling/src/vortex-deploy-artifact index b8a8867a4..c5cac72dd 100755 --- a/.vortex/tooling/src/vortex-deploy-artifact +++ b/.vortex/tooling/src/vortex-deploy-artifact @@ -136,5 +136,6 @@ if [ -n "${VORTEX_DEPLOY_ARTIFACT_CLEANUP_PATTERN}" ]; then fi "${TMPDIR:-/tmp}"/git-artifact "${artifact_args[@]}" -vvv +pass "Ran artifact builder." pass "Finished artifact deployment." diff --git a/.vortex/tooling/src/vortex-deploy-lagoon b/.vortex/tooling/src/vortex-deploy-lagoon index aebdf56b8..06e99bb02 100755 --- a/.vortex/tooling/src/vortex-deploy-lagoon +++ b/.vortex/tooling/src/vortex-deploy-lagoon @@ -95,7 +95,7 @@ is_lagoon_env_limit_exceeded() { # Run a Lagoon deploy command, surface its output, and translate an # environment-limit error into a controlled outcome. The reason for a failed -# deploy is carried in ${deploy_error} to be shown with the final status. +# deploy is carried in ${deploy_error} to be shown with the outcome. run_lagoon_deploy() { local deploy_output @@ -117,15 +117,31 @@ run_lagoon_deploy() { [ "${exit_code}" = "0" ] && [ "${VORTEX_DEBUG-}" = "1" ] && [ -n "${deploy_output}" ] && printf '%s\n' "${deploy_output}" # Always succeed: the outcome is carried in ${exit_code}. Leaking the status - # of the checks above would trip 'set -e' at the call site and skip the final - # deployment status line. + # of the checks above would trip 'set -e' at the call site before the deploy + # task is closed. return 0 } -# Track deploy status to report at the end. +# Close the open deploy task with the outcome carried in ${exit_code}. The +# argument is the noun phrase naming the deployment, used in both the success +# and the failure line. +close_deploy() { + if [ "${exit_code}" = "0" ]; then + pass "Requested ${1}" + return 0 + fi + + if [ -n "${deploy_error}" ]; then + printf '%s\n' "${deploy_error}" + fi + + fail "Failed to request ${1}" "${exit_code}" +} + +# Deploy status, carried from run_lagoon_deploy to close_deploy. exit_code=0 -# Raw CLI output of a failed deploy, printed with the final status. +# Raw CLI output of a failed deploy, printed with the failure line. deploy_error="" info "Started Lagoon deployment." @@ -245,32 +261,30 @@ else 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}" - # A failed deploy is reported by the final status line, after the - # override flag is restored. - [ "${exit_code}" = "0" ] && pass "Requested redeployment of environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, PR: ${VORTEX_DEPLOY_LAGOON_PR}." - # Lagoon reads the flag when it queues the build. - task "Waiting for deployment to be queued." + # Lagoon reads the flag while it queues the build, so the flag is restored + # only once the build has been queued. A failed deploy is reported after + # the restore. + note "Waiting for deployment to be queued." sleep 10 - pass "Waited for deployment to be queued." if [ "${override_db_state}" = "absent" ]; then - task "Removing a database import override flag." lagoon delete variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB || true - pass "Removed a database import override flag." + note "Removed a database import override flag." elif [ "${override_db_state}" = "present" ]; then - task "Restoring a database import override flag to ${override_db_original}." lagoon update variable --environment "${deploy_pr_full}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_original}" --scope "${override_db_scope}" || true - pass "Restored a database import override flag to ${override_db_original}." + 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}." + # Deployment of the fresh environment. else # Lagoon filters out the request and does not deploy when PR deployments # are not configured for the project. task "Deploying 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}" - [ "${exit_code}" = "0" ] && pass "Requested deployment of environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, PR: ${VORTEX_DEPLOY_LAGOON_PR}." + close_deploy "deployment of environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, PR: ${VORTEX_DEPLOY_LAGOON_PR}." fi # Deploy branch. @@ -342,42 +356,32 @@ else task "Redeploying environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}." run_lagoon_deploy deploy latest --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" - # A failed deploy is reported by the final status line, after the - # override flag is restored. - [ "${exit_code}" = "0" ] && pass "Requested redeployment of environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}." - # Lagoon reads the flag when it queues the build. - task "Waiting for deployment to be queued." + # Lagoon reads the flag while it queues the build, so the flag is restored + # only once the build has been queued. A failed deploy is reported after + # the restore. + note "Waiting for deployment to be queued." sleep 10 - pass "Waited for deployment to be queued." if [ "${override_db_state}" = "absent" ]; then - task "Removing a database import override flag." lagoon delete variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB || true - pass "Removed a database import override flag." + note "Removed a database import override flag." elif [ "${override_db_state}" = "present" ]; then - task "Restoring a database import override flag to ${override_db_original}." lagoon update variable --environment "${VORTEX_DEPLOY_LAGOON_BRANCH}" --name VORTEX_PROVISION_OVERRIDE_DB --value "${override_db_original}" --scope "${override_db_scope}" || true - pass "Restored a database import override flag to ${override_db_original}." + 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}." + # Deployment of the fresh environment. else # Lagoon filters out the request and does not deploy when the branch does # not match the configured deployment regex. task "Deploying environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}." run_lagoon_deploy deploy branch --branch "${VORTEX_DEPLOY_LAGOON_BRANCH}" - [ "${exit_code}" = "0" ] && pass "Requested deployment of environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}." + close_deploy "deployment of environment: project: ${VORTEX_DEPLOY_LAGOON_PROJECT}, branch: ${VORTEX_DEPLOY_LAGOON_BRANCH}." fi fi fi -if [ "${exit_code}" != "0" ]; then - if [ -n "${deploy_error}" ]; then - printf '%s\n' "${deploy_error}" - fi - - fail "Lagoon deployment completed with errors." "${exit_code}" -fi - pass "Finished Lagoon deployment." diff --git a/.vortex/tooling/src/vortex-export-db-image b/.vortex/tooling/src/vortex-export-db-image index 1978b0f2c..619687d9a 100755 --- a/.vortex/tooling/src/vortex-export-db-image +++ b/.vortex/tooling/src/vortex-export-db-image @@ -80,7 +80,7 @@ mkdir -p "$(dirname "${archive_file}")" docker save -o "${archive_file}" "${new_image}" if [ -f "${archive_file}" ] && [ -s "${archive_file}" ]; then - note "Exported database image saved to archive file ${archive_file}." + pass "Exported database image saved to archive file ${archive_file}." else # LCOV_EXCL_START fail "Unable to save database image archive file ${archive_file}." diff --git a/.vortex/tooling/src/vortex-fetch-db-acquia b/.vortex/tooling/src/vortex-fetch-db-acquia index 973e42088..03730e331 100755 --- a/.vortex/tooling/src/vortex-fetch-db-acquia +++ b/.vortex/tooling/src/vortex-fetch-db-acquia @@ -167,6 +167,7 @@ if [ "${VORTEX_FETCH_DB_FRESH}" = "1" ]; then max_wait="${VORTEX_FETCH_DB_ACQUIA_BACKUP_MAX_WAIT}" wait_interval="${VORTEX_FETCH_DB_ACQUIA_BACKUP_WAIT_INTERVAL}" elapsed=0 + backup_completed=0 while [ ${elapsed} -lt "${max_wait}" ]; do sleep "${wait_interval}" @@ -178,7 +179,7 @@ if [ "${VORTEX_FETCH_DB_FRESH}" = "1" ]; then status=$(echo "${status_json}" | extract_json_value "status") if [ "${status}" = "completed" ]; then - pass "Backup completed successfully." + backup_completed=1 break elif [ "${status}" = "failed" ]; then fail "Backup creation failed. API response: ${status_json}" @@ -187,10 +188,12 @@ if [ "${VORTEX_FETCH_DB_FRESH}" = "1" ]; then note "Backup in progress (${elapsed}s elapsed)..." done - if [ ${elapsed} -ge "${max_wait}" ]; then + if [ "${backup_completed}" = "0" ]; then fail "Backup creation timed out after ${max_wait} seconds." fi + pass "Backup completed successfully." + note "Fresh backup will be fetched." fi diff --git a/.vortex/tooling/src/vortex-push-container-registry b/.vortex/tooling/src/vortex-push-container-registry index 42f8a091f..02fb87a14 100755 --- a/.vortex/tooling/src/vortex-push-container-registry +++ b/.vortex/tooling/src/vortex-push-container-registry @@ -84,7 +84,7 @@ for key in "${!services[@]}"; do service="${services[${key}]}" image="${images[${key}]}" - task "Processing service ${service}." + note "Processing service ${service}." cid=$(docker compose ps -q "${service}") [ -z "${cid}" ] && fail "Service \"${service}\" is not running." @@ -106,7 +106,7 @@ for key in "${!services[@]}"; do docker push "${new_image}" pass "Pushed container image to the registry." - pass "Processed service ${service}." + note "Processed service ${service}." done pass "Finished container registry push." diff --git a/.vortex/tooling/src/vortex-setup-ssh b/.vortex/tooling/src/vortex-setup-ssh index d5b991ce9..5b8c6eb8e 100755 --- a/.vortex/tooling/src/vortex-setup-ssh +++ b/.vortex/tooling/src/vortex-setup-ssh @@ -78,15 +78,22 @@ if [ -n "${fingerprint-}" ]; then if [ "${fingerprint#SHA256:}" != "${fingerprint}" ]; then task "Searching for MD5 hash as fingerprint starts with SHA256." + matched_file="" for existing_file in "${HOME}"/.ssh/id_rsa*; do fingerprint_sha256=$(ssh-keygen -l -E sha256 -f "${existing_file}" | awk '{print $2}') if [ "${fingerprint_sha256}" = "${fingerprint}" ]; then - pass "Found matching existing key file ${existing_file}." + matched_file="${existing_file}" fingerprint=$(ssh-keygen -l -E md5 -f "${existing_file}" | awk '{print $2}') fingerprint="${fingerprint#MD5:}" break fi done + + if [ -n "${matched_file}" ]; then + pass "Found matching existing key file ${matched_file}." + else + pass "Did not find a matching existing key file." + fi fi file="${fingerprint//:/}" @@ -109,7 +116,7 @@ fi if ssh-add -l | grep -q "${file}"; then note "SSH agent already has ${file} key loaded." else - task "SSH agent does not have a required key loaded. Trying to load." + note "SSH agent does not have a required key loaded. Trying to load." if [ "${VORTEX_SSH_REMOVE_ALL_KEYS-}" = "1" ]; then task "Removing all keys from the SSH agent." @@ -117,6 +124,7 @@ else pass "Removed all keys from the SSH agent." fi + task "Loading key ${file} into the SSH agent." ssh-add "${file}" ssh-add -l pass "Loaded key ${file} into the SSH agent." diff --git a/.vortex/tooling/src/vortex-task-copy-db-acquia b/.vortex/tooling/src/vortex-task-copy-db-acquia index 47af6c8ce..49dc8a505 100755 --- a/.vortex/tooling/src/vortex-task-copy-db-acquia +++ b/.vortex/tooling/src/vortex-task-copy-db-acquia @@ -104,7 +104,7 @@ task "Copying database from ${VORTEX_TASK_COPY_DB_ACQUIA_SRC} to ${VORTEX_TASK_C task_status_json=$(curl -X POST -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" -H "Content-Type: application/json" -d "{\"source\":\"${src_env_id}\", \"name\":\"${VORTEX_TASK_COPY_DB_ACQUIA_NAME}\"}" "https://cloud.acquia.com/api/environments/${dst_env_id}/databases") notification_url=$(echo "${task_status_json}" | extract_json_value "_links" | extract_json_value "notification" | extract_json_value "href") -echo -n " > Checking task status: " +printf " Checking task status: " task_completed=0 # shellcheck disable=SC2034 for i in $(seq 1 "${VORTEX_TASK_COPY_DB_ACQUIA_STATUS_RETRIES}"); do @@ -114,11 +114,10 @@ for i in $(seq 1 "${VORTEX_TASK_COPY_DB_ACQUIA_STATUS_RETRIES}"); do task_state=$(echo "${task_status_json}" | extract_json_value "status") [ "${task_state}" = "completed" ] && task_completed=1 && break - task "Retrieving authentication token." token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_COPY_DB_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_COPY_DB_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") token=$(echo "${token_json}" | extract_json_value "access_token") [ -z "${token}" ] && fail "Unable to retrieve a token." - pass "Retrieved authentication token." + note "Refreshed authentication token." done echo diff --git a/.vortex/tooling/src/vortex-task-copy-files-acquia b/.vortex/tooling/src/vortex-task-copy-files-acquia index 21cd64fb9..99e45f402 100755 --- a/.vortex/tooling/src/vortex-task-copy-files-acquia +++ b/.vortex/tooling/src/vortex-task-copy-files-acquia @@ -100,7 +100,7 @@ task "Copying files from ${VORTEX_TASK_COPY_FILES_ACQUIA_SRC} to ${VORTEX_TASK_C task_status_json=$(curl -X POST -s -L -H 'Accept: application/json, version=2' -H "Authorization: Bearer ${token}" -H "Content-Type: application/json" -d "{\"source\":\"${src_env_id}\"}" "https://cloud.acquia.com/api/environments/${dst_env_id}/files") notification_url=$(echo "${task_status_json}" | extract_json_value "_links" | extract_json_value "notification" | extract_json_value "href") -echo -n " > Checking task status: " +printf " Checking task status: " task_completed=0 # shellcheck disable=SC2034 for i in $(seq 1 "${VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_RETRIES}"); do @@ -110,11 +110,10 @@ for i in $(seq 1 "${VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_RETRIES}"); do task_state=$(echo "${task_status_json}" | extract_json_value "status") [ "${task_state}" = "completed" ] && task_completed=1 && break - task "Retrieving authentication token." token_json=$(curl -s -L https://accounts.acquia.com/api/auth/oauth/token --data-urlencode "client_id=${VORTEX_TASK_COPY_FILES_ACQUIA_KEY}" --data-urlencode "client_secret=${VORTEX_TASK_COPY_FILES_ACQUIA_SECRET}" --data-urlencode "grant_type=client_credentials") token=$(echo "${token_json}" | extract_json_value "access_token") [ -z "${token}" ] && fail "Unable to retrieve a token." - pass "Retrieved authentication token." + note "Refreshed authentication token." done echo diff --git a/.vortex/tooling/src/vortex-task-purge-cache-acquia b/.vortex/tooling/src/vortex-task-purge-cache-acquia index 706766d40..6e7b3020b 100755 --- a/.vortex/tooling/src/vortex-task-purge-cache-acquia +++ b/.vortex/tooling/src/vortex-task-purge-cache-acquia @@ -139,7 +139,7 @@ if [ "${#domain_list[@]}" -gt 0 ]; then break fi - echo -n " > Checking task status: " + printf " Checking task status: " task_completed=0 # shellcheck disable=SC2034 for i in $(seq 1 "${VORTEX_TASK_PURGE_CACHE_ACQUIA_STATUS_RETRIES}"); do @@ -153,11 +153,10 @@ if [ "${#domain_list[@]}" -gt 0 ]; then break 1 fi - 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") token=$(echo "${token_json}" | extract_json_value "access_token") [ -z "${token}" ] && fail "Unable to retrieve a token." - pass "Retrieved authentication token." + note "Refreshed authentication token." done echo diff --git a/.vortex/tooling/tests/unit/deploy-artifact.bats b/.vortex/tooling/tests/unit/deploy-artifact.bats index 4e0dbc0bf..c2e5d5a96 100644 --- a/.vortex/tooling/tests/unit/deploy-artifact.bats +++ b/.vortex/tooling/tests/unit/deploy-artifact.bats @@ -156,6 +156,7 @@ load ../_helper.bash "Copying deployment .gitignore as it may not exist in deploy code source files." "Copied deployment .gitignore." "Running artifact builder." + "Ran artifact builder." "Finished artifact deployment." ) mocks="$(steps_run "setup")" @@ -207,6 +208,7 @@ load ../_helper.bash "@sha256sum -c" "@chmod +x ${TMPDIR:-/tmp}/git-artifact" "Running artifact builder." + "Ran artifact builder." "Finished artifact deployment." ) mocks="$(steps_run "setup")" diff --git a/.vortex/tooling/tests/unit/deploy-lagoon.bats b/.vortex/tooling/tests/unit/deploy-lagoon.bats index 4ff60e93d..f9959d1b6 100644 --- a/.vortex/tooling/tests/unit/deploy-lagoon.bats +++ b/.vortex/tooling/tests/unit/deploy-lagoon.bats @@ -127,13 +127,11 @@ load ../_helper.bash "Updated a database import override flag to 0." "Redeploying environment: project: test_project, branch: test-branch." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy latest --environment test-branch" - "Requested redeployment of environment: project: test_project, branch: test-branch." "Waiting for deployment to be queued." "@sleep 10" - "Waited for deployment to be queued." - "Restoring a database import override flag to 0." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project update variable --environment test-branch --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope GLOBAL" "Restored a database import override flag to 0." + "Requested redeployment of environment: project: test_project, branch: test-branch." "Finished Lagoon deployment." ) @@ -179,13 +177,11 @@ load ../_helper.bash "Updated a database import override flag to 1." "Redeploying environment: project: test_project, branch: test-branch." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy latest --environment test-branch" - "Requested redeployment of environment: project: test_project, branch: test-branch." "Waiting for deployment to be queued." "@sleep 10" - "Waited for deployment to be queued." - "Restoring a database import override flag to 0." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project update variable --environment test-branch --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope GLOBAL" "Restored a database import override flag to 0." + "Requested redeployment of environment: project: test_project, branch: test-branch." "Finished Lagoon deployment." ) @@ -228,7 +224,6 @@ load ../_helper.bash "Added a database import override flag with value 1." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy latest --environment test-branch" "@sleep 10" - "Removing a database import override flag." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project delete variable --environment test-branch --name VORTEX_PROVISION_OVERRIDE_DB" "Removed a database import override flag." "Finished Lagoon deployment." @@ -266,7 +261,6 @@ load ../_helper.bash "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project update variable --environment test-branch --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope BUILD" "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy latest --environment test-branch" "@sleep 10" - "Restoring a database import override flag to 1." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project update variable --environment test-branch --name VORTEX_PROVISION_OVERRIDE_DB --value 1 --scope BUILD" "Restored a database import override flag to 1." "Finished Lagoon deployment." @@ -396,13 +390,11 @@ load ../_helper.bash "Updated a database import override flag to 0." "Redeploying environment: project: test_project, PR: 123." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy pullrequest --number 123 --base-branch-name develop --base-branch-ref origin/develop --head-branch-name feature-branch --head-branch-ref origin/feature-branch --title pr-123" - "Requested redeployment of environment: project: test_project, PR: 123." "Waiting for deployment to be queued." "@sleep 10" - "Waited for deployment to be queued." - "Restoring a database import override flag to 0." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project update variable --environment pr-123 --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope GLOBAL" "Restored a database import override flag to 0." + "Requested redeployment of environment: project: test_project, PR: 123." "Finished Lagoon deployment." ) @@ -452,13 +444,11 @@ load ../_helper.bash "Updated a database import override flag to 1." "Redeploying environment: project: test_project, PR: 456." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy pullrequest --number 456 --base-branch-name develop --base-branch-ref origin/develop --head-branch-name feature-branch --head-branch-ref origin/feature-branch --title pr-456" - "Requested redeployment of environment: project: test_project, PR: 456." "Waiting for deployment to be queued." "@sleep 10" - "Waited for deployment to be queued." - "Restoring a database import override flag to 0." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project update variable --environment pr-456 --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope GLOBAL" "Restored a database import override flag to 0." + "Requested redeployment of environment: project: test_project, PR: 456." "Finished Lagoon deployment." ) @@ -498,7 +488,6 @@ load ../_helper.bash "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project add variable --environment pr-456 --name VORTEX_PROVISION_OVERRIDE_DB --value 1 --scope global" "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy pullrequest --number 456 --base-branch-name develop --base-branch-ref origin/develop --head-branch-name feature-branch --head-branch-ref origin/feature-branch --title pr-456" "@sleep 10" - "Removing a database import override flag." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project delete variable --environment pr-456 --name VORTEX_PROVISION_OVERRIDE_DB" "Removed a database import override flag." "Finished Lagoon deployment." @@ -539,7 +528,6 @@ load ../_helper.bash "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project update variable --environment pr-456 --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope BUILD" "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy pullrequest --number 456 --base-branch-name develop --base-branch-ref origin/develop --head-branch-name feature-branch --head-branch-ref origin/feature-branch --title pr-456" "@sleep 10" - "Restoring a database import override flag to 1." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project update variable --environment pr-456 --name VORTEX_PROVISION_OVERRIDE_DB --value 1 --scope BUILD" "Restored a database import override flag to 1." "Finished Lagoon deployment." @@ -697,7 +685,7 @@ load ../_helper.bash "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy branch --branch test-branch # 1 # ${limit_error}" "- Requested deployment of environment: project: test_project, branch: test-branch." "Lagoon environment limit exceeded." - "[FAIL] Lagoon deployment completed with errors." + "[FAIL] Failed to request deployment of environment: project: test_project, branch: test-branch." "would exceed the configured limit" ) @@ -803,7 +791,7 @@ load ../_helper.bash "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project list environments --output-json --pretty # {\"data\":[]}" "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy branch --branch test-branch # 1 # ${deploy_error}" "Error: deployment rejected by policy." - "[FAIL] Lagoon deployment completed with errors." + "[FAIL] Failed to request deployment of environment: project: test_project, branch: test-branch." ) mocks="$(steps_run "setup")" @@ -836,7 +824,7 @@ load ../_helper.bash "Deploying environment: project: test_project, branch: test-branch." "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy branch --branch test-branch # 1 # ${deploy_error}" "- Requested deployment of environment: project: test_project, branch: test-branch." - "[FAIL] Lagoon deployment completed with errors." + "[FAIL] Failed to request deployment of environment: project: test_project, branch: test-branch." "Error: deployment rejected by policy." ) @@ -849,7 +837,7 @@ load ../_helper.bash popd >/dev/null } -@test "Failure: deploy error survives the tasks that follow a failed redeploy" { +@test "Failure: failed redeploy restores the override flag before reporting the failure" { pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 fixture_ssh_key_prepare @@ -877,7 +865,7 @@ load ../_helper.bash "@sleep 10" "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project update variable --environment test-branch --name VORTEX_PROVISION_OVERRIDE_DB --value 0 --scope GLOBAL" "Restored a database import override flag to 0." - "[FAIL] Lagoon deployment completed with errors." + "[FAIL] Failed to request redeployment of environment: project: test_project, branch: test-branch." "Error: deployment rejected by policy." ) @@ -905,7 +893,7 @@ load ../_helper.bash "@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 list environments --output-json --pretty # {\"data\":[]}" "@lagoon --force --skip-update-check --ssh-key ${HOME}/.ssh/id_rsa --lagoon amazeeio --project test_project deploy branch --branch test-branch # 1" - "[FAIL] Lagoon deployment completed with errors." + "[FAIL] Failed to request deployment of environment: project: test_project, branch: test-branch." ) mocks="$(steps_run "setup")" diff --git a/.vortex/tooling/tests/unit/fetch-db-acquia.bats b/.vortex/tooling/tests/unit/fetch-db-acquia.bats index c8b546062..e837bc7bb 100644 --- a/.vortex/tooling/tests/unit/fetch-db-acquia.bats +++ b/.vortex/tooling/tests/unit/fetch-db-acquia.bats @@ -733,6 +733,84 @@ bats_require_minimum_version 1.5.0 popd >/dev/null } +@test "fetch-db-acquia: Fresh backup completing on the last allowed poll is not a timeout" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + rm -rf .data + mkdir -p .data + + declare -a STEPS=( + "[INFO] Started database dump fetch from Acquia." + + # Authentication + "[TASK] 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","expires_in":3600}' + + # Application UUID + "[TASK] 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","name":"testapp"}]}}' + + # Environment ID + "[TASK] Retrieving prod 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%3Dprod # {"_embedded":{"items":[{"id":"env-id-456","name":"prod"}]}}' + + # Create backup + "[TASK] Creating new database backup for testdb." + '@curl -s -L -X POST -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/environments/env-id-456/databases/testdb/backups # {"_links":{"notification":{"href":"https://cloud.acquia.com/api/notifications/notification-uuid-123"}}}' + + # Wait for backup - completes on the poll that exhausts the wait budget. + "[TASK] Waiting for backup to complete." + '@sleep 5 # 0' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/notifications/notification-uuid-123 # {"status":"in-progress"}' + " Backup in progress (5s elapsed)..." + '@sleep 5 # 0' + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/notifications/notification-uuid-123 # {"status":"completed"}' + "[ OK ] Backup completed successfully." + "- Backup creation timed out" + " Fresh backup will be fetched." + + # Continue with normal download flow + "[TASK] Discovering latest backup ID for database testdb." + '@curl --progress-bar -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/environments/env-id-456/databases/testdb/backups?sort=created # {"_embedded":{"items":[{"id":"backup-id-new-123","completed":"2024-01-02T00:00:00+00:00"}]}}' + + "[TASK] Discovering backup URL." + '@curl -s -L -H Accept: application/json, version=2 -H Authorization: Bearer test-token https://cloud.acquia.com/api/environments/env-id-456/databases/testdb/backups/backup-id-new-123/actions/download # {"url":"https://backup.example.com/db-fresh.sql.gz"}' + + "[TASK] Fetching database dump into file .data/testdb_backup_backup-id-new-123.sql.gz." + '@curl --progress-bar -L https://backup.example.com/db-fresh.sql.gz -o .data/testdb_backup_backup-id-new-123.sql.gz # 0 # # echo "CREATE TABLE fresh (id INT);" | gzip > .data/testdb_backup_backup-id-new-123.sql.gz' + + "[TASK] Expanding database file .data/testdb_backup_backup-id-new-123.sql.gz into .data/testdb_backup_backup-id-new-123.sql." + "@gunzip -t .data/testdb_backup_backup-id-new-123.sql.gz # 0" + "@gunzip -c .data/testdb_backup_backup-id-new-123.sql.gz # 0 # CREATE TABLE fresh (id INT);" + + '[TASK] Renaming file ".data/testdb_backup_backup-id-new-123.sql" to ".data/db.sql".' + '@mv .data/testdb_backup_backup-id-new-123.sql .data/db.sql # 0 # # echo "CREATE TABLE fresh (id INT);" > .data/db.sql' + + "[ OK ] Finished database dump fetch from Acquia." + ) + + export VORTEX_FETCH_DB_ACQUIA_KEY="test-key" + export VORTEX_FETCH_DB_ACQUIA_SECRET="test-secret" + export VORTEX_FETCH_DB_ACQUIA_APP_NAME="testapp" + export VORTEX_FETCH_DB_ENVIRONMENT="prod" + export VORTEX_FETCH_DB_ACQUIA_DB_NAME="testdb" + export VORTEX_FETCH_DB_ACQUIA_DB_DIR=".data" + export VORTEX_FETCH_DB_ACQUIA_DB_FILE="db.sql" + export VORTEX_FETCH_DB_FRESH="1" + export VORTEX_FETCH_DB_ACQUIA_BACKUP_MAX_WAIT="10" + export VORTEX_FETCH_DB_ACQUIA_BACKUP_WAIT_INTERVAL="5" + + mocks="$(steps_run "setup")" + run .vortex/tooling/src/vortex-fetch-db-acquia + steps_run "assert" "${mocks[@]}" + + assert_success + assert_file_exists ".data/db.sql" + + rm -rf .data + popd >/dev/null +} + @test "fetch-db-acquia: Backup URL discovery fails when response has no URL" { pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 diff --git a/.vortex/tooling/tests/unit/setup-ssh.bats b/.vortex/tooling/tests/unit/setup-ssh.bats index 40efd93c3..f597528a5 100644 --- a/.vortex/tooling/tests/unit/setup-ssh.bats +++ b/.vortex/tooling/tests/unit/setup-ssh.bats @@ -471,3 +471,34 @@ load ../_helper.bash popd >/dev/null } + +@test "Key provided, SHA256 fingerprint, No matching key file => failure" { + pushd "${LOCAL_REPO_DIR}" >/dev/null || exit 1 + + export VORTEX_DEBUG=1 + + fixture_ssh_key_prepare + fixture_ssh_key + export VORTEX_SSH_PREFIX="TEST" + export VORTEX_TEST_SSH_FINGERPRINT="SHA256:NOTAREALFINGERPRINT" + + # Override the values that could be coming from the environment with defaults. + export VORTEX_SSH_REMOVE_ALL_KEYS="0" + export VORTEX_SSH_DISABLE_STRICT_HOST_KEY_CHECKING="0" + + # shellcheck disable=SC2034 + declare -a STEPS=( + "Using fingerprint-based deploy key because fingerprint was provided." + "Searching for MD5 hash as fingerprint starts with SHA256." + "Did not find a matching existing key file." + "- Found matching existing key file" + "SSH key file ${HOME}/.ssh/id_rsa_SHA256NOTAREALFINGERPRINT does not exist." + ) + mocks="$(steps_run "setup")" + + run .vortex/tooling/src/vortex-setup-ssh + assert_failure + steps_run "assert" "${mocks[@]}" + + popd >/dev/null +}