|
|
@@ -0,0 +1,189 @@
|
|
|
+#!/bin/bash
|
|
|
+set -euo pipefail
|
|
|
+
|
|
|
+ENV="dev"
|
|
|
+PROJECT_NAME="LQMDRetrievalFront"
|
|
|
+BASE_DIR="/home/lq/lq_workspace/${PROJECT_NAME}"
|
|
|
+SRC_DIR="${BASE_DIR}/source/${PROJECT_NAME}"
|
|
|
+COMPOSE_RUN_DIR="${BASE_DIR}/app/docker"
|
|
|
+COMPOSE_FILE_DIR="${BASE_DIR}/app/docker"
|
|
|
+COMPOSE_FILE_NAME="docker-compose.${ENV}.yaml"
|
|
|
+SERVICES=("LQMDRetrievalFront")
|
|
|
+
|
|
|
+VERSION_SOURCE_SERVICE="${SERVICES[0]}"
|
|
|
+COMPOSE_FILE_PATH="${COMPOSE_FILE_DIR}/${COMPOSE_FILE_NAME}"
|
|
|
+
|
|
|
+log_info() {
|
|
|
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*"
|
|
|
+}
|
|
|
+
|
|
|
+log_error() {
|
|
|
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" >&2
|
|
|
+}
|
|
|
+
|
|
|
+require_path() {
|
|
|
+ local type=$1
|
|
|
+ local path=$2
|
|
|
+ if [ "$type" = "file" ] && [ ! -f "$path" ]; then
|
|
|
+ log_error "file does not exist: $path"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+ if [ "$type" = "dir" ] && [ ! -d "$path" ]; then
|
|
|
+ log_error "directory does not exist: $path"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+}
|
|
|
+
|
|
|
+get_current_version() {
|
|
|
+ local service=$1
|
|
|
+ local compose_file=$2
|
|
|
+ local image_line
|
|
|
+ image_line=$(grep -A 20 "^[[:space:]]*${service}:" "$compose_file" | grep -E "^[[:space:]]*image:" | head -1) || {
|
|
|
+ log_error "image field not found for service: $service"
|
|
|
+ exit 1
|
|
|
+ }
|
|
|
+ image_line=$(echo "$image_line" | sed 's/#.*$//')
|
|
|
+ echo "$image_line" | sed -E 's/.*:([^:]*)$/\1/' | tr -d '[:space:]'
|
|
|
+}
|
|
|
+
|
|
|
+increment_version() {
|
|
|
+ local ver=$1
|
|
|
+ if [[ ! $ver =~ ^v[0-9]+\.[0-9]+$ ]]; then
|
|
|
+ log_error "invalid version format, expected v0.01 or v1.2, got: $ver"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+ local prefix="${ver%.*}"
|
|
|
+ local last_digit="${ver##*.}"
|
|
|
+ local len=${#last_digit}
|
|
|
+ local num=$((10#$last_digit + 1))
|
|
|
+ local new_last
|
|
|
+ new_last=$(printf "%0${len}d" "$num")
|
|
|
+ echo "${prefix}.${new_last}"
|
|
|
+}
|
|
|
+
|
|
|
+update_compose_version_for_services() {
|
|
|
+ local compose_file=$1
|
|
|
+ local new_version=$2
|
|
|
+ shift 2
|
|
|
+ local services=("$@")
|
|
|
+
|
|
|
+ cp "$compose_file" "${compose_file}.bak"
|
|
|
+ for svc in "${services[@]}"; do
|
|
|
+ sed -i -E "/^[[:space:]]*${svc}:/,/^[[:space:]]*image:/ s/(image:[[:space:]]*.*:)([^[:space:]#]*)([[:space:]]*#.*)?$/\1${new_version}\3/" "$compose_file"
|
|
|
+ log_info "updated $svc image version to $new_version"
|
|
|
+ done
|
|
|
+}
|
|
|
+
|
|
|
+get_image_repository() {
|
|
|
+ local service=$1
|
|
|
+ local compose_file=$2
|
|
|
+ local image_line
|
|
|
+ image_line=$(grep -A 20 "^[[:space:]]*${service}:" "$compose_file" | grep -E "^[[:space:]]*image:" | head -1)
|
|
|
+ image_line=$(echo "$image_line" | sed 's/#.*$//')
|
|
|
+ echo "$image_line" | sed -E 's/^[[:space:]]*image:[[:space:]]*//' | sed -E 's/:[^:]*$//' | tr -d '[:space:]'
|
|
|
+}
|
|
|
+
|
|
|
+cleanup_old_images_for_services() {
|
|
|
+ local compose_file=$1
|
|
|
+ shift
|
|
|
+ local services=("$@")
|
|
|
+
|
|
|
+ for svc in "${services[@]}"; do
|
|
|
+ local image_repo
|
|
|
+ image_repo=$(get_image_repository "$svc" "$compose_file")
|
|
|
+ mapfile -t all_tags < <(docker images --format "{{.Repository}}:{{.Tag}}" | grep "^${image_repo}:" | sed "s|${image_repo}:||" | sort -V)
|
|
|
+ if [ ${#all_tags[@]} -le 3 ]; then
|
|
|
+ log_info "$svc image count <= 3, skip cleanup"
|
|
|
+ continue
|
|
|
+ fi
|
|
|
+ local to_delete=$(( ${#all_tags[@]} - 3 ))
|
|
|
+ for (( i=0; i<to_delete; i++ )); do
|
|
|
+ docker rmi "${image_repo}:${all_tags[$i]}" || log_error "failed to remove image ${image_repo}:${all_tags[$i]}"
|
|
|
+ done
|
|
|
+ done
|
|
|
+}
|
|
|
+
|
|
|
+check_all_containers_running() {
|
|
|
+ local services=("$@")
|
|
|
+ for i in {1..15}; do
|
|
|
+ sleep 2
|
|
|
+ local all_running=true
|
|
|
+ for svc in "${services[@]}"; do
|
|
|
+ local container_id
|
|
|
+ container_id=$(docker compose -f "$COMPOSE_FILE_PATH" ps -q "$svc")
|
|
|
+ local status
|
|
|
+ status=$(docker inspect --format='{{.State.Status}}' "$container_id" 2>/dev/null || echo "not_found")
|
|
|
+ if [ "$status" != "running" ]; then
|
|
|
+ all_running=false
|
|
|
+ log_info "$svc status: $status, waiting..."
|
|
|
+ break
|
|
|
+ fi
|
|
|
+ done
|
|
|
+ if [ "$all_running" = true ]; then
|
|
|
+ log_info "all target services are running"
|
|
|
+ return 0
|
|
|
+ fi
|
|
|
+ done
|
|
|
+ return 1
|
|
|
+}
|
|
|
+
|
|
|
+stop_and_remove_containers() {
|
|
|
+ local services=("$@")
|
|
|
+ for svc in "${services[@]}"; do
|
|
|
+ docker compose -f "$COMPOSE_FILE_PATH" stop "$svc" >/dev/null 2>&1 || true
|
|
|
+ docker compose -f "$COMPOSE_FILE_PATH" rm -f "$svc" >/dev/null 2>&1 || true
|
|
|
+ done
|
|
|
+}
|
|
|
+
|
|
|
+start_containers() {
|
|
|
+ local services=("$@")
|
|
|
+ docker compose -f "$COMPOSE_FILE_PATH" up -d "${services[@]}"
|
|
|
+}
|
|
|
+
|
|
|
+require_path file "$COMPOSE_FILE_PATH"
|
|
|
+require_path dir "$SRC_DIR"
|
|
|
+require_path dir "$COMPOSE_RUN_DIR"
|
|
|
+
|
|
|
+log_info "pull source code: $SRC_DIR"
|
|
|
+cd "$SRC_DIR"
|
|
|
+current_branch=$(git symbolic-ref --short HEAD)
|
|
|
+log_info "current branch: $current_branch"
|
|
|
+git pull origin "$current_branch"
|
|
|
+git log -1 --format="[latest commit] %H %ad %s" --date=format:'%Y-%m-%d %H:%M:%S'
|
|
|
+
|
|
|
+cd "$COMPOSE_RUN_DIR"
|
|
|
+CURRENT_VERSION=$(get_current_version "$VERSION_SOURCE_SERVICE" "$COMPOSE_FILE_PATH")
|
|
|
+NEW_VERSION=$(increment_version "$CURRENT_VERSION")
|
|
|
+log_info "version: $CURRENT_VERSION -> $NEW_VERSION"
|
|
|
+
|
|
|
+update_compose_version_for_services "$COMPOSE_FILE_PATH" "$NEW_VERSION" "${SERVICES[@]}"
|
|
|
+
|
|
|
+for svc in "${SERVICES[@]}"; do
|
|
|
+ log_info "build service: $svc"
|
|
|
+ DOCKER_BUILDKIT=1 docker compose -f "$COMPOSE_FILE_PATH" build "$svc" || {
|
|
|
+ log_error "build failed, restore compose file"
|
|
|
+ mv "${COMPOSE_FILE_PATH}.bak" "$COMPOSE_FILE_PATH"
|
|
|
+ exit 1
|
|
|
+ }
|
|
|
+done
|
|
|
+
|
|
|
+stop_and_remove_containers "${SERVICES[@]}"
|
|
|
+start_containers "${SERVICES[@]}"
|
|
|
+
|
|
|
+if check_all_containers_running "${SERVICES[@]}"; then
|
|
|
+ log_info "deploy succeeded, running version: $NEW_VERSION"
|
|
|
+ cleanup_old_images_for_services "$COMPOSE_FILE_PATH" "${SERVICES[@]}"
|
|
|
+ rm -f "${COMPOSE_FILE_PATH}.bak"
|
|
|
+else
|
|
|
+ log_error "new version failed, rollback to: $CURRENT_VERSION"
|
|
|
+ stop_and_remove_containers "${SERVICES[@]}"
|
|
|
+ mv "${COMPOSE_FILE_PATH}.bak" "$COMPOSE_FILE_PATH"
|
|
|
+ start_containers "${SERVICES[@]}"
|
|
|
+ check_all_containers_running "${SERVICES[@]}" || {
|
|
|
+ log_error "rollback failed, manual check required"
|
|
|
+ exit 1
|
|
|
+ }
|
|
|
+ exit 1
|
|
|
+fi
|
|
|
+
|
|
|
+log_info "deploy finished"
|