Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions .vortex/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
37 changes: 26 additions & 11 deletions .vortex/docs/content/contributing/maintenance/template.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions .vortex/tests/phpunit/Functional/DeploymentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions .vortex/tooling/src/vortex-deploy-artifact
Original file line number Diff line number Diff line change
Expand Up @@ -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."
74 changes: 39 additions & 35 deletions .vortex/tooling/src/vortex-deploy-lagoon
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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."
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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."
2 changes: 1 addition & 1 deletion .vortex/tooling/src/vortex-export-db-image
Original file line number Diff line number Diff line change
Expand Up @@ -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}."
Expand Down
7 changes: 5 additions & 2 deletions .vortex/tooling/src/vortex-fetch-db-acquia
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand All @@ -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}"
Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions .vortex/tooling/src/vortex-push-container-registry
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand All @@ -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."
12 changes: 10 additions & 2 deletions .vortex/tooling/src/vortex-setup-ssh
Original file line number Diff line number Diff line change
Expand Up @@ -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//:/}"
Expand All @@ -109,14 +116,15 @@ 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."
ssh-add -D
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."
Expand Down
5 changes: 2 additions & 3 deletions .vortex/tooling/src/vortex-task-copy-db-acquia
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
5 changes: 2 additions & 3 deletions .vortex/tooling/src/vortex-task-copy-files-acquia
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
5 changes: 2 additions & 3 deletions .vortex/tooling/src/vortex-task-purge-cache-acquia
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
Loading