#!/usr/bin/env bash
set -euo pipefail

# Этот скрипт обновляет Gitlab, следуя пути миграции, который, по утверждению Gitlab docs/tools, безопасен.
# https://gitlab-com.gitlab.io/support/toolbox/upgrade-path/

APP_DATA_DIR="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"

GITLAB_RAILS_VERSION_FILE="${APP_DATA_DIR}/data/data/gitlab-rails/VERSION"

APP_COMPOSE_FILE="${APP_DATA_DIR}/docker-compose.yml"
APP_COMPOSE_BACKUP_FILE="${APP_DATA_DIR}/docker-compose.yml.bak"

# Список версий на пути миграции
# gitlab в магазине приложений DFWebOS изначально был выпущен с версией 17.2.1.
# инструмент пути миграции: https://gitlab-com.gitlab.io/support/toolbox/upgrade-path/
VERSIONS=()
VERSIONS+=("17.2.1")
VERSIONS+=("17.3.3")
VERSIONS+=("17.5.1")
# Для образа zengxs/gitlab не существует финального патча 17.5.3, который можно было бы обновить до версии 17.6.
VERSIONS+=("17.6.1")
VERSIONS+=("17.7.0")
VERSIONS+=("17.8.1")
VERSIONS+=("17.9.1")
VERSIONS+=("17.10.1")
VERSIONS+=("17.11.1")
VERSIONS+=("18.0.2")
VERSIONS+=("18.2.1")
VERSIONS+=("18.4.0")
VERSIONS+=("18.5.1")
VERSIONS+=("18.6.2")
VERSIONS+=("18.7.4")
VERSIONS+=("18.8.4")
VERSIONS+=("18.9.3")
VERSIONS+=("18.10.1")

# Список изображений на пути миграции
# Использование zengxs/gitlab, у которого могут быть не все версии, перечисленные в инструменте пути обновления: https://hub.docker.com/r/zengxs/gitlab/tags
IMAGES=()
IMAGES+=("zengxs/gitlab:17.2.1-ce.0@sha256:ac08a4dd997b6cd5d00d56c0027629de56ac80d9d30f9c4f75a73da73f5ff1b4")
IMAGES+=("zengxs/gitlab:17.3.3-ce.0@sha256:b4369fc8f2a505fdf30bae7fa2befde2a8d6f75c067e5bcf85aeb1c5f345cda0")
IMAGES+=("zengxs/gitlab:17.5.1-ce.0@sha256:61cb4c79fe55de9dc94822d5a64a07ee40ff681d6282ab5733bc8e80479451ff")
IMAGES+=("zengxs/gitlab:17.6.1-ce.0@sha256:fac3ab00c189e7f7ef2d9170abe1028180663f5bb50dd8b84cb79c4f3053df42")
IMAGES+=("zengxs/gitlab:17.7.0-ce.0@sha256:a353041094142d3f3192295d0973e660778db5f7637069f8e035afe45c6b88d1")
IMAGES+=("zengxs/gitlab:17.8.1-ce.0@sha256:963a719edd95cc7c237e9ca4914d75d1ca9f472a3001b76ad9dfb1dad65b4732")
IMAGES+=("zengxs/gitlab:17.9.1-ce.0@sha256:8087be9de83abf3f215694d1fc52d90a0c2a882589016a0b784598723e871325")
IMAGES+=("zengxs/gitlab:17.10.1-ce.0@sha256:c64afa93f40b9cffc1350da62b67ffefdd13123874b595bccfdc389514069f58")
IMAGES+=("zengxs/gitlab:17.11.1-ce.0@sha256:05d73cabf1dbff39b93e7ad0c1ea6bbcbc29642185774cb59e31664f17b0a134")
IMAGES+=("zengxs/gitlab:18.0.2-ce.0@sha256:107ee285aea892fa6ed8e039df8a6d850aa5d3469247e9783dcf765789b70872")
IMAGES+=("gitlab/gitlab-ce:18.2.1-ce.0@sha256:3db5de3ce9fba4511345bb67731a1787e316f73029cdbd309ee947f860d9ff00")
IMAGES+=("gitlab/gitlab-ce:18.4.0-ce.0@sha256:42d4f499ed8f055e91028d87772f8ea65ba4812bd71a54086cfc4aa84604ab7a")
IMAGES+=("gitlab/gitlab-ce:18.5.1-ce.0@sha256:6db4caefb0597c966065f4c3307b60aa00d13559bbe9b0c3b17bc32f0d239ab1")
IMAGES+=("gitlab/gitlab-ce:18.6.2-ce.0@sha256:a4ffd3cf42e6554fcb8a1735cea40ae2e46bec6f31b518f04132a93d7f48ffcf")
IMAGES+=("gitlab/gitlab-ce:18.7.4-ce.0@sha256:9890c6c76625070ae420784f1d6d157498a75b6f65d075c0e1e55c2a25a3f5ab")
IMAGES+=("gitlab/gitlab-ce:18.8.4-ce.0@sha256:0a2e4a47b0c3f8b2682f4a4406f2d1ad0713024bd4e849355b07e93fbf85049b")
IMAGES+=("gitlab/gitlab-ce:18.9.3-ce.0@sha256:eec7f29ca83c8b54ecb1154c5c36b87a67c6b9cedaea763868b014e6a0539756")
IMAGES+=("gitlab/gitlab-ce:18.10.1-ce.0@sha256:6024d9f2a8d52dd56ffa8b510fcfc368d7ddaed1102f76ed6c419289e68921b0")

find_index() {
	local -r value="${1}"
	shift
	local -r array=("$@")

	for i in "${!array[@]}"; do
		if [[ "${array[$i]}" == "${value}" ]]; then
			echo $i
			exit
		fi
	done

	echo -1
}

wait_for_migrations_complete() {
	local start_time=$(date +%s)
	local max_wait_time=$((1 * 60 * 60))  # 1 hour in seconds

	while true; do
		# Мы установили некоторое максимальное время ожидания, чтобы предотвратить бесконечный цикл, если что-то пошло не так.
		# Возможно, в будущем нам захочется решить эту проблему лучше.
		local current_time=$(date +%s)
		local elapsed_time=$((current_time - start_time))
		if [[ ${elapsed_time} -ge ${max_wait_time} ]]; then
			echo "Maximum migration wait time of 1 hour exceeded. Exiting as something is likely wrong..."
			exit 1
		fi
		
		# Установите +e, потому что эта команда завершится неудачно, пока gitlab-psql не будет готов принимать соединения, и мы не хотим завершать сценарий в случае сбоя.
		set +e
		echo "Running gitlab-psql command to check if batched migrations are complete"
		# https://docs.gitlab.com/ee/update/background_migrations.html#from-the-database
		command_output=$(docker exec gitlab_gitlab_1 gitlab-psql -tAc "SELECT COUNT(*) FROM batched_background_migrations WHERE status NOT IN(3, 6);" 2>&1)
		# Передача команды psql в scripts/app Compose приводит к разделению аргументов команды, что приводит к ошибкам psql.
		# Использование приведенной выше команды `docker exec` позволяет обойти эту проблему, но нам следует вернуться к этому в будущем, чтобы отказаться от привязки этого сценария к Docker.
		# command_output=$("${DFWEBOS_ROOT}/scripts/app" compose "${APP_ID}" exec gitlab gitlab-psql -tAc "SELECT COUNT(*) FROM batched_background_migrations WHERE status NOT IN(3, 6);" 2>&1)
		exit_code=$?
		# Установите -e, чтобы восстановить поведение по умолчанию.
		set -e

		if [[ ${exit_code} -eq 0 ]]; then
			remaining_migrations=$(echo "${command_output}" | tr -d '[:space:]')
			echo "Parsed remaining batched migrations: '${remaining_migrations}'"

      # Мы хотим продолжить только в том случае, если осталось "0" пакетных миграций.
			if [[ "${remaining_migrations}" == "0" ]]; then
				echo "GitLab migration completed successfully."
				return
			fi
		else
			echo "Command failed with exit code ${exit_code}. Error output: ${command_output}"
		fi

		echo "Waiting 30 seconds before checking migration status again..."
		sleep 30
	done
}

check_compose_file() {
	local -r image="${1}"

	gitlab_image=$(cat "${APP_COMPOSE_FILE}" 2>/dev/null | yq '.services.gitlab.image' || true)

	if [[ "${gitlab_image}" != "${image}" ]]; then
		echo "The docker-compose.yml now looks bad. Restoring..."

		mv "${APP_COMPOSE_BACKUP_FILE}" "${APP_COMPOSE_FILE}"

		exit
	fi
}

get_version() {
	cat "${GITLAB_RAILS_VERSION_FILE}"
}

# Здесь начинается выполнение основного скрипта
# ================================

# Если файл версии gitlab-rails еще не существует
# Тогда скорее всего это новая установка
# Поэтому делать нечего
if [[ ! -f "${GITLAB_RAILS_VERSION_FILE}" ]]; then
	exit
fi

current_version=$(get_version)
echo "Current GitLab version: ${current_version}"

# проверьте, добавлен ли к текущей версии "-patch", и выйдите, если это так, мы позволяем миграции завершиться
# мы добавляем "-patch" в версию ниже во время миграции
if [[ "${current_version}" == *"-patch"* ]]; then
	echo "Active GitLab version is undergoing migration. Exiting..."
	exit
fi

# Проверьте, есть ли активная версия в списке миграции
active_version_idx=$(find_index "${current_version}" "${VERSIONS[@]}")
if [[ "${active_version_idx}" == "-1" ]]; then
	echo "Active version is not supported in the list of migrations"
	exit
fi

# Проверьте, обновлено ли уже
if [[ "${VERSIONS[-1]}" == "${current_version}" ]]; then
	echo "GitLab is already on the latest major version. No migration needed."
	exit
fi

# Защита от ошибок логики миграции из-за дрейфа списка version/image.
if [[ "${#VERSIONS[@]}" -ne "${#IMAGES[@]}" ]]; then
	echo "Migration configuration error: VERSIONS (${#VERSIONS[@]}) and IMAGES (${#IMAGES[@]}) length mismatch."
	exit 1
fi

# Перебирать версии, игнорируя предыдущие версии.
for i in "${!VERSIONS[@]}"; do
	[[ "${i}" -le "${active_version_idx}" ]] && continue

	version="${VERSIONS[$i]}"
	image="${IMAGES[$i]}"

	echo "Migrating to version: ${version} (${image})"
	echo

	cp --archive "${APP_COMPOSE_FILE}" "${APP_COMPOSE_BACKUP_FILE}"

	yq -i ".services.gitlab.image = \"${image}\"" "${APP_COMPOSE_FILE}"

	check_compose_file "${image}"

	# Отметить текущую версию как находящуюся в середине обновления.
	current_version=$(get_version)
	sed -i "s/${current_version}/${current_version}-patch/" "${GITLAB_RAILS_VERSION_FILE}"

	# Запустить приложение
	# перехват перед запуском будет выполнен в подоболочке, но завершится при проверке -patch, чтобы позволить завершить миграцию
	"${DFWEBOS_ROOT}/scripts/app" start gitlab

	echo "Waiting for update to complete..."

	# Подождите, пока приложение выполнит миграцию.
	# Указывается выходными данными бегуна gitlab-rails
	wait_for_migrations_complete

	# Остановить приложение
	"${DFWEBOS_ROOT}/scripts/app" stop gitlab

	# Удалить изображение промежуточной версии
	# Если это не последнее изображение
	# В противном случае его придется загрузить заново
	if [[ "${version}" != "${VERSIONS[-1]}" ]]; then
		echo "Deleting intermediary image: ${image}"

		docker rmi "${image}" || true
	fi
done

# Удалить файл резервной копии
rm -rf "${APP_COMPOSE_BACKUP_FILE}"

echo "Migration completed successfully"
