From fac8b5ddfe3b2e886fa3c301c2fbb2227caa4c8f Mon Sep 17 00:00:00 2001 From: "Yao, Qing" Date: Fri, 23 May 2025 08:58:44 +0800 Subject: [PATCH 1/3] Init Chatqna one click deployment Signed-off-by: Yao, Qing --- ChatQnA/deployment/check_env.sh | 315 ++++++++++ ChatQnA/deployment/configure.sh | 95 +++ ChatQnA/deployment/install_chatqna.sh | 509 ++++++++++++++++ ChatQnA/deployment/one_click_chatqna.sh | 312 ++++++++++ ChatQnA/deployment/set_values.sh | 320 +++++++++++ ChatQnA/deployment/test_connection.sh | 494 ++++++++++++++++ ChatQnA/deployment/update_images.sh | 734 ++++++++++++++++++++++++ ChatQnA/deployment/utils.sh | 87 +++ 8 files changed, 2866 insertions(+) create mode 100644 ChatQnA/deployment/check_env.sh create mode 100644 ChatQnA/deployment/configure.sh create mode 100644 ChatQnA/deployment/install_chatqna.sh create mode 100644 ChatQnA/deployment/one_click_chatqna.sh create mode 100644 ChatQnA/deployment/set_values.sh create mode 100644 ChatQnA/deployment/test_connection.sh create mode 100644 ChatQnA/deployment/update_images.sh create mode 100644 ChatQnA/deployment/utils.sh diff --git a/ChatQnA/deployment/check_env.sh b/ChatQnA/deployment/check_env.sh new file mode 100644 index 0000000000..9b3448a8ac --- /dev/null +++ b/ChatQnA/deployment/check_env.sh @@ -0,0 +1,315 @@ +#!/bin/bash +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# Script's own directory +SCRIPT_DIR_ENV=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +# Source utility functions +source "${SCRIPT_DIR_ENV}/utils.sh" # Assuming utils.sh is in the same directory + +# =================== CONFIG =================== +REQUIRED_CMDS=("git" "docker" "curl" "vim" "nproc") # Added nproc for CPU check +THRESHOLD_GB=5 +CHATQNA_MIN_CPU=96 +CHATQNA_MIN_HPU=8 +# LOG_FILE is now managed by utils.sh, can be overridden here if needed: +# LOG_FILE="check_env.log" # Specific log file for this script + +# ================ ARG PARSING ================= +DEVICE="" # Standardized to DEVICE +DEPLOYMENT_MODE="online" # Default to online, can be overridden + +while [[ $# -gt 0 ]]; do + case "$1" in + --log-file) + LOG_FILE="$2" # Allow overriding utils.sh default + shift 2 + ;; + --device) # Changed from --xeon/--gaudi to --device xeon/gaudi + DEVICE="$2" + if [[ "$DEVICE" != "xeon" && "$DEVICE" != "gaudi" ]]; then + log ERROR "Invalid --device: $DEVICE. Must be 'xeon' or 'gaudi'." + exit 1 + fi + shift 2 + ;; + --mode) # For online/offline package installation + DEPLOYMENT_MODE="$2" + if [[ "$DEPLOYMENT_MODE" != "online" && "$DEPLOYMENT_MODE" != "offline" ]]; then + log ERROR "--mode must be either 'online' or 'offline'." + exit 1 + fi + shift 2 + ;; + *) + log WARN "Unknown argument: $1" + shift + ;; + esac +done + +if [[ -z "$DEVICE" ]]; then + log ERROR "Usage: $0 --device [xeon|gaudi] [--mode online|offline] [--log-file ]" + exit 1 +fi + +log INFO "Starting environment check for DEVICE: $DEVICE, MODE: $DEPLOYMENT_MODE" + +# ================ DRIVER CHECKS ============= +check_habana_modules() { + log INFO "Checking Habana kernel modules..." + local missing=0 + local required_modules=("habanalabs" "habanalabs_cn" "habanalabs_ib" "habanalabs_en") + for module in "${required_modules[@]}"; do + if ! lsmod | grep -q "^${module}\s"; then + log ERROR "Module missing: ${module}" + missing=$((missing + 1)) + else + log OK "Module loaded: ${module}" + fi + done + if [ $missing -gt 0 ]; then + log ERROR "$missing required Habana modules missing. Please ensure Habana drivers are correctly installed." + return 1 # Changed to return 1 for main to catch + else + log OK "All required Habana modules loaded." + return 0 + fi +} + +check_habana_sw() { + log INFO "Listing installed Habana packages..." + if ! apt list --installed 2>/dev/null | grep habana | tee -a "$LOG_FILE"; then + log WARN "No Habana packages found via 'apt list'. This might be normal if installed differently, or an issue." + fi +} + +hl_qual_test() { + log INFO "Starting Gaudi Function Tests (hl_qual)..." + # Check if hl_qual exists + if ! command_exists /opt/habanalabs/qual/gaudi2/bin/hl_qual; then + log ERROR "hl_qual command not found at /opt/habanalabs/qual/gaudi2/bin/hl_qual. Skipping test." + return 1 + fi + export HABANA_LOGS="/tmp/hl_qual.log" + # Temporarily cd for test, ensure we cd back + local current_dir=$(pwd) + cd /opt/habanalabs/qual/gaudi2/bin || { log ERROR "Failed to cd to hl_qual directory."; return 1; } + + # Run test, capture output and exit code + # Reduced verbosity for general log, full output to $LOG_FILE + # Consider making '-l extreme -t 2' configurable or less aggressive for a quick check + output=$(./hl_qual -gaudi2 -c all -rmod parallel -f2 -l extreme -t 2 2>&1) + exit_code=$? + cd "$current_dir" # Go back to original directory + + echo "$output" >> "$LOG_FILE" # Log full output + + if [ $exit_code -ne 0 ]; then + log ERROR "hl_qual test failed (exit code: $exit_code). Check $LOG_FILE for details." + return 1 + fi + + # More robust parsing of PASSED + if echo "$output" | grep -q "hl qual report" && echo "$output" | grep -q "PASSED"; then + log OK "hl_qual test PASSED." + else + log ERROR "hl_qual test FAILED (did not detect 'PASSED' after 'hl qual report'). Check $LOG_FILE for details." + return 1 + fi + return 0 +} + +check_hl_cards() { + log INFO "Checking Habana HPU cards via hl-smi..." + if ! command_exists hl-smi; then + log ERROR "hl-smi command not found. Cannot check HPU cards." + return 1 + fi + local total_cards available_cards + # Ensure hl-smi output is parsable, handle errors + if ! hl_smi_output=$(hl-smi); then + log ERROR "hl-smi command failed to execute properly." + return 1 + fi + + total_cards=$(echo "$hl_smi_output" | grep -E '^\| +[0-9]+ +HL-' | wc -l) + # Assuming 'N/A' in a specific column indicates available for use (this might need verification based on actual hl-smi output for "available") + # A more robust check might be needed depending on what "available" truly means in context of hl-smi + available_cards=$(echo "$hl_smi_output" | grep -E '^\| +[0-9]+ +N/A' | wc -l) # This interpretation of "available" might need review + + log INFO "Total HL-SMI cards detected: $total_cards" + log INFO "Cards reported as 'N/A' (interpreted as available): $available_cards" + + if [ "$available_cards" -lt "$CHATQNA_MIN_HPU" ]; then + log ERROR "Available HPU cards ($available_cards) is less than required minimum ($CHATQNA_MIN_HPU)." + return 1 + else + log OK "Sufficient HPU cards available ($available_cards >= $CHATQNA_MIN_HPU)." + fi + return 0 +} + +get_cpu_info() { + log INFO "Checking CPU information..." + local cpu_model total_cores + cpu_model=$(grep -m1 'model name' /proc/cpuinfo | cut -d ':' -f2 | sed 's/^[[:space:]]*//') + + # Using nproc --all for logical cores, or lscpu for physical if preferred. + if command_exists nproc; then + total_cores=$(nproc --all) + elif command_exists lscpu; then # Fallback if nproc is not available + total_cores=$(lscpu | grep '^CPU(s):' | awk '{print $2}') + else + log WARN "Cannot determine CPU core count (nproc and lscpu not found). Skipping CPU core check." + return 0 # Or return 1 if this is critical + fi + + log INFO "CPU Model: $cpu_model" + log INFO "Total logical CPU cores: $total_cores" + + if [ "$total_cores" -lt "$CHATQNA_MIN_CPU" ]; then + log ERROR "Detected CPU cores ($total_cores) is less than required minimum ($CHATQNA_MIN_CPU)." + return 1 + else + log OK "CPU core count ($total_cores) is sufficient." + fi + + log INFO "Attempting to set CPU governor to performance mode..." + if echo "performance" | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor > /dev/null 2>&1; then + log OK "CPU governor set to performance mode for all cores." + else + log WARN "Failed to set CPU governor to performance. This might require sudo privileges or a different path." + fi + return 0 +} + +# ================ SOFTWARE CHECKS =============== + +install_docker() { + subsection_header "Installing Docker" + + # install docker + if ! sudo apt install -y docker.io &>> "$LOG_FILE"; then + log ERROR "Failed to install Docker." + return 1 + fi + + sudo chmod 666 /var/run/docker.sock + + # install docker compose + DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker} + mkdir -p $DOCKER_CONFIG/cli-plugins + curl -SL https://github.com/docker/compose/releases/download/v2.36.0/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose + chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose + + if docker compose version &>> "$LOG_FILE"; then + log OK "Docker and Docker Compose successfully installed." + else + log ERROR "Docker Compose installation failed." + return 1 + fi + + # set docker proxy + sudo mkdir -p /etc/systemd/system/docker.service.d + HTTP_PROXY=${http_proxy:-} + HTTPS_PROXY=${https_proxy:-} + NO_PROXY=${no_proxy:-} + + sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf > /dev/null < /dev/null; then + log ERROR "$cmd is not installed, try to install it..." + + if [[ "$cmd" == "docker" ]]; then + log INFO "Installing Docker and Docker Compose..." + if ! install_docker; then + all_ready=false + continue + fi + else + log INFO "Attempting to install $cmd..." + apt-get update -qq + apt-get install -y "$cmd" &>> "$LOG_FILE" + + if command -v "$cmd" &> /dev/null; then + log OK "$cmd successfully installed." + else + log ERROR "Failed to install $cmd automatically." + all_ready=false + fi + fi + else + log OK "$cmd is available." + fi + done + $all_ready || return 1 +} + +# ================ DISK SPACE CHECKS =============== +check_disk_space() { + log INFO "Checking available disk space on / ..." + local available_gb + # df -BG / might not be portable, -P is more POSIX compliant, awk for calculation + available_bytes=$(df -P / | awk 'NR==2 {print $4}') + available_gb=$((available_bytes / 1024 / 1024)) # KiloBytes to GigaBytes + + log INFO "Available disk space: ${available_gb}GB" + if (( available_gb < THRESHOLD_GB )); then + log ERROR "Not enough disk space. Required: ${THRESHOLD_GB}GB, Available: ${available_gb}GB." + return 1 + else + log OK "Disk space is sufficient (${available_gb}GB)." + fi + return 0 +} + +# ================ MAIN ======================== +main() { + local checks_failed=0 + + section_header "HARDWARE & DRIVER ENVIRONMENT CHECK" + if [[ "$DEVICE" == "xeon" ]]; then + get_cpu_info || checks_failed=$((checks_failed + 1)) + elif [[ "$DEVICE" == "gaudi" ]]; then + # For Gaudi, CPU info is also relevant + get_cpu_info || checks_failed=$((checks_failed + 1)) + check_habana_modules || checks_failed=$((checks_failed + 1)) + check_habana_sw # This is informational, not typically a fail condition + check_hl_cards || checks_failed=$((checks_failed + 1)) + hl_qual_test || checks_failed=$((checks_failed + 1)) + fi + + section_header "SOFTWARE DEPENDENCIES CHECK" + check_required_commands || checks_failed=$((checks_failed + 1)) + + section_header "DISK SPACE CHECK" + check_disk_space || checks_failed=$((checks_failed + 1)) + + if [ $checks_failed -gt 0 ]; then + log ERROR "Environment check failed with $checks_failed error(s). Please review the logs." + exit 1 + else + log OK "All environment checks passed. System should be ready for deployment." + fi +} + +main \ No newline at end of file diff --git a/ChatQnA/deployment/configure.sh b/ChatQnA/deployment/configure.sh new file mode 100644 index 0000000000..1409dbdb06 --- /dev/null +++ b/ChatQnA/deployment/configure.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +set -e +set -o pipefail + + +echo "USE WITH CAUTION THIS SCRIPT USES SUDO PRIVILAGES TO INSTALL NEEDED PACKAGES LOCALLY AND CONFIGURE THEM. \ +USING IT MAY OVERWRITE EXISTING CONFIGURATION. Press ctrl+c to cancel. Sleeping for 30s." && sleep 30 + +usage() { + echo "Usage: $0 -g HUG_TOKEN [-p HTTP_PROXY] [-u HTTPS_PROXY] [-n NO_PROXY]" + exit 1 +} + +# Function to check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# !TODO this should be changed to use non-positional parameters +# Parse command-line arguments +while getopts "p:u:n:" opt; do + case $opt in + p) RAG_HTTP_PROXY="$OPTARG";; + u) RAG_HTTPS_PROXY="$OPTARG";; + n) RAG_NO_PROXY="$OPTARG,.cluster.local";; + *) usage ;; + esac +done + +# Update package list +sudo apt-get update -q + +# Install packages +sudo apt-get install -y -q build-essential make zip jq apt-transport-https ca-certificates curl software-properties-common + +# Install Docker if not already installed +if command_exists docker; then + echo "Docker is already installed." +else + curl -fsSL https://get.docker.com -o get-docker.sh + sudo bash get-docker.sh --version 25.0.1 + rm get-docker.sh + + sudo usermod -aG docker "$USER" + + if command_exists docker; then + echo "Docker installation successful." + else + echo "Docker installation failed." + exit 1 + fi +fi + +# Configure Docker proxy settings if provided +if [[ -n "$RAG_HTTP_PROXY" || "$RAG_HTTPS_PROXY" || "$RAG_NO_PROXY" ]]; then + export RAG_HTTP_PROXY + export RAG_HTTPS_PROXY + export RAG_NO_PROXY + envsubst < tpl/config.json.tpl > tmp.config.json + if [ -e ~/.docker/config.json ]; then + echo "Warning! Docker config.json exists; continues using the existing file" + else + if [ ! -d ~/.docker/ ]; then + mkdir ~/.docker + fi + mv tmp.config.json ~/.docker/config.json + sudo systemctl restart docker + echo "Created Docker config.json, restarting docker.service" + fi +fi + +# Install Helm if not already installed +if command_exists helm; then + echo "Helm is already installed." +else + curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 + bash get_helm.sh --version 3.16.1 + rm get_helm.sh + + if command_exists helm; then + echo "Helm installation successful." + else + echo "Helm installation failed." + exit 1 + fi +fi + +# OpenTelemetry contrib journals/systemd collector requires plenty of inotify instances or it fails +# without error occurs: "Insufficient watch descriptors available. Reverting to -n." (in journalctl receiver) +[[ $(sudo sysctl -n fs.inotify.max_user_instances) -lt 8000 ]] && sudo sysctl -w fs.inotify.max_user_instances=8192 + +echo "All installations and configurations are complete." \ No newline at end of file diff --git a/ChatQnA/deployment/install_chatqna.sh b/ChatQnA/deployment/install_chatqna.sh new file mode 100644 index 0000000000..7c60593584 --- /dev/null +++ b/ChatQnA/deployment/install_chatqna.sh @@ -0,0 +1,509 @@ +#!/bin/bash +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# Script's own directory +SCRIPT_DIR_INSTALL=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${SCRIPT_DIR_INSTALL}/utils.sh" # Assuming utils.sh is in the same directory +# LOG_FILE="install_chatqna.log" # Optional: specific log file + +# Paths +repo_path=$(realpath "$SCRIPT_DIR_INSTALL/../") +manifests_path="$repo_path/deployment/kubernetes" +compose_path="$repo_path/deployment/docker_compose" +set_values_script_path="$SCRIPT_DIR_INSTALL/set_values.sh" + +# Namespaces +DEPLOYMENT_NS=chatqna +UI_NS=rag-ui # Used in clear_all_k8s_namespaces + +# Deployment variables +DEVICE="" # Target platform: gaudi, xeon +DEPLOY_MODE="docker" # k8s or docker +REGISTRY="" +TAG="latest" +MOUNT_DIR="" +EMBED_MODEL="" +RERANK_MODEL_ID="" +LLM_MODEL_ID="" +HUGGINGFACEHUB_API_TOKEN="" # Mandatory for actual model fetching if not cached +HTTP_PROXY="" +HTTPS_PROXY="" +NO_PROXY="" + +# Dynamically find available devices based on subdirectories in manifests_path (for K8s) or compose_path (for Docker) +# This provides a dynamic list for the help message if needed. +get_available_devices() { + local search_path="" + if [[ "$DEPLOY_MODE" == "k8s" && -d "$manifests_path" ]]; then + search_path="$manifests_path" + elif [[ "$DEPLOY_MODE" == "docker" && -d "$compose_path" ]]; then + search_path="$compose_path" + else + echo "gaudi,xeon" # Fallback + return + fi + # List subdirectories that presumably correspond to devices + (cd "$search_path" && find . -maxdepth 1 -mindepth 1 -type d -exec basename {} \; | paste -sd ',' || echo "gaudi,xeon") +} +available_devices=$(get_available_devices) # Call once or dynamically if DEPLOY_MODE changes before usage + +function usage() { + # Update available_devices if DEPLOY_MODE might change before this is called + # For simplicity, using the one determined at script start for now. + available_devices_for_help=$(get_available_devices) + echo -e "Usage: $0 --device --deploy-mode [OPTIONS]" + echo -e "Core Options:" + echo -e "\t--device : Specify the target device (e.g., $available_devices_for_help)." + echo -e "\t--deploy-mode : Specify deployment mode: 'k8s' or 'docker' (default: $DEPLOY_MODE)." + echo -e "\t--hf-token : (Required for most operations) Hugging Face Hub API token." + echo -e "Image Options:" + echo -e "\t--tag : Use specific image tag for deployment (default: $TAG)." + echo -e "\t--registry : Use specific container registry (e.g. mydockerhub/myproject)." + echo -e "Model Options (Overrides defaults set by set_values.sh if not provided here):" + echo -e "\t--mount-dir : Data mount directory for Docker volumes (default: ./data)." + echo -e "\t--embed-model : Specify the embedding model ID." + echo -e "\t--rerank-model :Specify the reranking model ID." + echo -e "\t--llm-model : Specify the LLM model ID." + echo -e "Proxy Options:" + echo -e "\t--http-proxy : HTTP proxy." + echo -e "\t--https-proxy :HTTPS proxy." + echo -e "\t--no-proxy : Comma-separated list of hosts to bypass proxy." + echo -e "Actions:" + echo -e "\t--deploy: (Default action if device and mode specified) Deploy services." + echo -e "\t--test: Run a connection test (requires services to be up)." + echo -e "\t-cd, --clear-deployment:Clear deployed services for the specified device and mode." + echo -e "\t-ca, --clear-all: Clear ALL ChatQnA related K8s namespaces (USE WITH CAUTION, K8s mode only)." + echo -e "\t-h, --help: Display this help message." + echo -e "Example: $0 --device gaudi --deploy-mode k8s --hf-token --tag mytag --deploy" +} + +# Removed helm_install - not used +# Removed start_and_keep_port_forwarding - not used + +check_pods_ready() { + local namespace="$1" + # Check for existence of pods first + if ! kubectl get pods -n "$namespace" --no-headers -o custom-columns="NAME:.metadata.name" 2>/dev/null | grep -q "."; then + log WARN "No pods found in namespace $namespace. Assuming this is okay if it's early in deployment." + return 0 # Or 1 if pods are strictly expected + fi + + # Get all pods, then filter for not-ready ones. + # This checks the "Ready" condition. + not_ready_pods_details=$(kubectl get pods -n "$namespace" -o jsonpath='{range .items[*]}{.metadata.name}{range .status.conditions[?(@.type=="Ready")]}{"\t"}{.status}{end}{"\n"}{end}' 2>/dev/null | grep -v "True$" ) + + if [ -n "$not_ready_pods_details" ]; then + # log INFO "Still waiting for: $not_ready_pods_details" # Too verbose for every check + return 1 # Found pods not ready + else + return 0 # All pods are ready (or no pods, handled above) + fi +} + +wait_for_pods_ready() { + local namespace="$1" + log INFO "Waiting for all pods in namespace '$namespace' to be ready..." + sleep 15 # Initial delay for resources to be created/settled + + local current_time + local timeout_seconds=2700 # 45 minutes + local end_time=$(( $(date +%s) + timeout_seconds )) + local print_interval=30 # Print status every 30 seconds + local next_print_time=$(date +%s) + + while true; do + current_time=$(date +%s) + if [[ "$current_time" -ge "$end_time" ]]; then + log ERROR "Timeout reached: Pods in namespace '$namespace' not ready after $((timeout_seconds / 60)) minutes." + kubectl get pods -n "$namespace" -o wide >> "$LOG_FILE" # Log current pod status + return 1 + fi + + if check_pods_ready "$namespace"; then + log OK "All pods in namespace '$namespace' are ready." + return 0 + else + if [[ "$current_time" -ge "$next_print_time" ]]; then + printf "." # Progress indicator + # log INFO "Still waiting for pods in $namespace..." # Optional more verbose log + next_print_time=$((current_time + print_interval)) + fi + sleep 10 # Check interval + fi + done +} + + +kill_processes_by_pattern() { + local pattern="$1" + local pids + mapfile -t pids < <(pgrep -f "$pattern") # -f matches against full argument list + + if [ ${#pids[@]} -eq 0 ]; then + log INFO "No processes found matching pattern: $pattern" + return + fi + + for pid in "${pids[@]}"; do + # Verify the command before killing, especially with broad patterns + local full_command + full_command=$(ps -p "$pid" -o cmd= --no-headers || true) # Get command, allow failure if PID vanished + if [ -z "$full_command" ]; then + log INFO "Process $pid (matching '$pattern') already gone." + continue + fi + + log INFO "Attempting to kill process '$full_command' (PID $pid) matching pattern '$pattern'" + # Try progressively stronger signals + if sudo kill -SIGINT "$pid" 2>/dev/null; then + sleep 1 + if ! ps -p "$pid" > /dev/null; then log INFO "PID $pid killed with SIGINT."; continue; fi + fi + if sudo kill -SIGTERM "$pid" 2>/dev/null; then + sleep 2 + if ! ps -p "$pid" > /dev/null; then log INFO "PID $pid killed with SIGTERM."; continue; fi + fi + if sudo kill -SIGKILL "$pid" 2>/dev/null; then + log INFO "PID $pid killed with SIGKILL." + else + log WARN "Failed to kill PID $pid. It might require manual intervention or already be gone." + fi + done +} + +function configure_services_via_set_values() { + section_header "Configuring services parameters (set_values.sh)" + if [ ! -f "$set_values_script_path" ]; then + log ERROR "set_values.sh script not found at $set_values_script_path. Cannot configure services." + return 1 + fi + + local set_values_options=() + # Mandatory for set_values.sh + set_values_options+=("-d" "$DEVICE") + set_values_options+=("-m" "$DEPLOY_MODE") + + # Optional ones, pass if set in this script + [ -n "$REGISTRY" ] && set_values_options+=("-r" "$REGISTRY") + [ -n "$TAG" ] && set_values_options+=("-t" "$TAG") + [ -n "$MOUNT_DIR" ] && set_values_options+=("-u" "$MOUNT_DIR") + [ -n "$EMBED_MODEL" ] && set_values_options+=("-e" "$EMBED_MODEL") + [ -n "$RERANK_MODEL_ID" ] && set_values_options+=("-a" "$RERANK_MODEL_ID") # -a for rerank in set_values.sh + [ -n "$LLM_MODEL_ID" ] && set_values_options+=("-l" "$LLM_MODEL_ID") # -l for llm in set_values.sh + [ -n "$HUGGINGFACEHUB_API_TOKEN" ] && set_values_options+=("-g" "$HUGGINGFACEHUB_API_TOKEN") + [ -n "$HTTP_PROXY" ] && set_values_options+=("-p" "$HTTP_PROXY") + [ -n "$HTTPS_PROXY" ] && set_values_options+=("-s" "$HTTPS_PROXY") # -s for https in set_values.sh + [ -n "$NO_PROXY" ] && set_values_options+=("-n" "$NO_PROXY") # -n for no_proxy additions + + if [ ${#set_values_options[@]} -gt 2 ]; then # At least -d and -m are there + log INFO "Running: bash $set_values_script_path ${set_values_options[*]}" + # Use `bash` not `source` if set_values.sh is standalone and doesn't need to modify current shell's env + if source "$set_values_script_path" "${set_values_options[@]}"; then + log OK "set_values.sh executed successfully." + else + log ERROR "set_values.sh failed. Exiting." + return 1 + fi + else + log INFO "No specific configurations to pass to set_values.sh beyond device and mode." + fi +} + +function start_k8s_deployment() { + local target_device="$1" + local k8s_device_manifest_dir="$manifests_path/$target_device/" + + section_header "Starting K8S deployment for DEVICE: $target_device" + + if [ ! -d "$k8s_device_manifest_dir" ]; then + log ERROR "K8s manifest directory $k8s_device_manifest_dir not found for device $target_device." + return 1 + fi + # Check if there are any .yaml or .yml files in the directory + if ! ls "$k8s_device_manifest_dir"/*.yaml >/dev/null 2>&1 && ! ls "$k8s_device_manifest_dir"/*.yml >/dev/null 2>&1; then + log ERROR "No YAML manifest files found in $k8s_device_manifest_dir." + return 1 + fi + + + log INFO "Creating namespace $DEPLOYMENT_NS if it doesn't exist (sudo for kubectl may be needed if not configured)." + if ! kubectl get namespace "$DEPLOYMENT_NS" > /dev/null 2>&1; then + kubectl create namespace "$DEPLOYMENT_NS" || { log ERROR "Failed to create namespace $DEPLOYMENT_NS."; return 1; } + fi + + log INFO "Applying manifests from $k8s_device_manifest_dir to namespace $DEPLOYMENT_NS." + # kubectl apply -f directory/ will apply all .yaml, .yml, .json files + if kubectl apply -f "$k8s_device_manifest_dir" -n "$DEPLOYMENT_NS"; then + log OK "Manifests applied successfully." + else + log ERROR "Failed to apply manifests from $k8s_device_manifest_dir. Exiting." + return 1 + fi + + wait_for_pods_ready "$DEPLOYMENT_NS" || return 1 # Exit if pods don't become ready + log OK "All pods in $DEPLOYMENT_NS are ready." +} + +function clear_k8s_deployment() { + # This function clears a specific deployment namespace, not all. + # The DEVICE parameter is not strictly needed here if we only use DEPLOYMENT_NS. + section_header "Clearing K8S deployment in namespace $DEPLOYMENT_NS" + log INFO "Deleting namespace $DEPLOYMENT_NS..." + if kubectl get ns "$DEPLOYMENT_NS" > /dev/null 2>&1; then + if kubectl delete ns "$DEPLOYMENT_NS" --wait=true --timeout=5m; then # Added --wait and timeout + log OK "Namespace $DEPLOYMENT_NS deleted successfully." + else + log WARN "Failed to delete namespace $DEPLOYMENT_NS or deletion timed out. It might be stuck in Terminating state." + log WARN "You may need to manually clean up resources in $DEPLOYMENT_NS." + fi + else + log INFO "Namespace $DEPLOYMENT_NS does not exist, nothing to clear." + fi + log INFO "Attempting to kill any lingering kubectl port-forward processes for $DEPLOYMENT_NS (sudo may be required)..." + # Be specific with pkill to avoid killing unrelated port-forwards if possible + kill_processes_by_pattern "kubectl port-forward.*--namespace +$DEPLOYMENT_NS" +} + +function start_docker_deployment() { + local target_device="$1" + local target_compose_dir="$compose_path/$target_device" + + section_header "Starting Docker Compose deployment for DEVICE: $target_device" + + if [ ! -d "$target_compose_dir" ]; then + log ERROR "Docker Compose directory $target_compose_dir not found for device $target_device." + return 1 + fi + if [ ! -f "$target_compose_dir/compose.yaml" ] && [ ! -f "$target_compose_dir/docker-compose.yaml" ]; then + log ERROR "No compose.yaml or docker-compose.yaml found in $target_compose_dir." + return 1 + fi + + log INFO "Changing directory to $target_compose_dir" + pushd "$target_compose_dir" > /dev/null || { log ERROR "Failed to cd into $target_compose_dir."; return 1; } + + # Source the set_env.sh if it exists, to make sure env vars are set for docker compose + if [ -f "./set_env.sh" ]; then + log INFO "Sourcing set_env.sh for Docker environment..." + source ./set_env.sh + else + log WARN "set_env.sh not found in $target_compose_dir. Docker compose will use existing environment or .env file." + fi + + log INFO "Starting Docker Compose services in detached mode (sudo for docker may be needed)..." + # Use 'docker compose' (v2 syntax) preferentially + local compose_cmd="docker compose" + + if $compose_cmd up -d; then + log OK "Docker Compose services for $target_device started successfully." + else + log ERROR "Docker Compose for $target_device failed to start. Check logs above." + $compose_cmd ps >> "$LOG_FILE" # Log status of containers + $compose_cmd logs --tail="50" >> "$LOG_FILE" # Log last 50 lines from services + popd > /dev/null + return 1 + fi + popd > /dev/null # Go back to original script dir + log INFO "Docker deployment started. Services should be running in the background." +} + +function clear_docker_deployment() { + local target_device="$1" + local target_compose_dir="$compose_path/$target_device" + + section_header "Clearing Docker Compose deployment for DEVICE: $target_device" + + if [ ! -d "$target_compose_dir" ];then + log INFO "Docker Compose directory $target_compose_dir not found. Assuming already cleared or never deployed." + return + fi + if [ ! -f "$target_compose_dir/compose.yaml" ] && [ ! -f "$target_compose_dir/docker-compose.yaml" ]; then + log WARN "No compose.yaml or docker-compose.yaml found in $target_compose_dir. Cannot run 'down'." + return + fi + + log INFO "Changing directory to $target_compose_dir" + pushd "$target_compose_dir" > /dev/null || { log ERROR "Failed to cd into $target_compose_dir."; return 1; } + + local compose_cmd="docker compose" # Default to v2 + + log INFO "Stopping and removing Docker Compose services (sudo for docker may be needed)..." + # Add --remove-orphans to remove containers for services not defined in the Compose file + # Add -v to remove named volumes declared in the `volumes` section of the Compose file + if $compose_cmd down --remove-orphans -v; then + log OK "Docker Compose services for $target_device stopped and removed successfully." + else + log WARN "Docker Compose down command failed for $target_device. Some resources might remain." + # Don't exit with error, allow script to continue if part of a larger cleanup + fi + popd > /dev/null # Go back +} + +function clear_all_k8s_namespaces_dangerously() { + section_header "DANGER ZONE: Removing ALL ChatQnA related K8s namespaces" + # Add all relevant namespaces here + local namespaces_to_clear=("$DEPLOYMENT_NS" "$UI_NS") # Add others as needed + + log WARN "This will attempt to delete the following K8s namespaces: ${namespaces_to_clear[*]}" + log WARN "This is a destructive operation. Ensure you want to proceed." + read -p "Type 'YES' to confirm deletion of these namespaces: " confirmation + if [[ "$confirmation" != "YES" ]]; then + log INFO "Namespace deletion aborted by user." + return + fi + + for ns in "${namespaces_to_clear[@]}"; do + log INFO "Attempting to delete namespace $ns (sudo for kubectl may be needed)..." + if kubectl get ns "$ns" > /dev/null 2>&1; then + if kubectl delete ns "$ns" --wait=true --timeout=5m; then + log OK "Namespace $ns deleted." + else + log WARN "Failed to delete namespace $ns or timed out. It might be stuck in Terminating state." + fi + else + log INFO "Namespace $ns does not exist." + fi + done + log INFO "Attempting to kill ALL lingering kubectl port-forward processes (sudo may be required)..." + kill_processes_by_pattern "kubectl port-forward" # Kills all kubectl port-forwards, be cautious +} + +# --- Argument Parsing --- +action="deploy" # Default action + +# Parse arguments +# Using long options for better readability +while [[ "$#" -gt 0 ]]; do + case $1 in + --device) + DEVICE="$2"; shift ;; + --deploy-mode) + DEPLOY_MODE="$2"; shift ;; + --tag) + TAG="$2"; shift ;; + --registry) + REGISTRY="$2"; shift ;; + --mount-dir) + MOUNT_DIR="$2"; shift ;; + --embed-model) + EMBED_MODEL="$2"; shift ;; + --rerank-model) + RERANK_MODEL_ID="$2"; shift ;; + --llm-model) + LLM_MODEL_ID="$2"; shift ;; + --hf-token) + HUGGINGFACEHUB_API_TOKEN="$2"; shift ;; + --http-proxy) + HTTP_PROXY="$2"; shift ;; + --https-proxy) + HTTPS_PROXY="$2"; shift ;; + --no-proxy) + NO_PROXY="$2"; shift ;; + --deploy) # Explicit deploy action + action="deploy" ;; + --test) + action="test" ;; + -cd|--clear-deployment) + action="clear-deployment" ;; + -ca|--clear-all) + action="clear-all" ;; + -h|--help) + usage; exit 0 ;; + *) + log ERROR "Unknown option or action: $1" + usage; exit 1 ;; + esac + shift +done + + +# Validate core parameters based on action +if [[ "$action" == "deploy" || "$action" == "clear-deployment" ]]; then + if [ -z "$DEVICE" ]; then + log ERROR "--device is required for action '$action'." + usage; exit 1 + fi + # DEPLOY_MODE has a default, so it's always set. Validate it. + if [[ "$DEPLOY_MODE" != "k8s" && "$DEPLOY_MODE" != "docker" ]]; then + log ERROR "Invalid --deploy-mode '$DEPLOY_MODE'. Must be 'k8s' or 'docker'." + usage; exit 1 + fi + if [[ "$action" == "deploy" && -z "$HUGGINGFACEHUB_API_TOKEN" ]]; then + # Warn, but don't exit, if models might be locally cached or don't need HF token + log WARN "HUGGINGFACEHUB_API_TOKEN (--hf-token) is not set. Deployment might fail if models need to be downloaded." + fi +fi + +if [[ "$action" == "clear-all" && "$DEPLOY_MODE" != "k8s" ]]; then + log WARN "--clear-all is primarily for K8s. Specified --deploy-mode '$DEPLOY_MODE' will be ignored. Action targets K8s." +fi + + +# --- Main Action Logic --- +case "$action" in + deploy) + log INFO "Selected action: Deploy $DEVICE using $DEPLOY_MODE" + if ! configure_services_via_set_values; then + log ERROR "Service configuration failed. Aborting deployment." + exit 1 + fi + + if [ "$DEPLOY_MODE" = "k8s" ]; then + start_k8s_deployment "$DEVICE" || exit 1 + elif [ "$DEPLOY_MODE" = "docker" ]; then + start_docker_deployment "$DEVICE" || exit 1 + fi + log OK "Deployment of $DEVICE finished." + log INFO "Waiting a bit for services to fully initialize before testing..." + progress_bar 180 + + log INFO "Proceeding with connection test..." + if [ -f "$SCRIPT_DIR_INSTALL/test_connection.sh" ]; then + if ! bash "$SCRIPT_DIR_INSTALL/test_connection.sh" --deploy-mode "$DEPLOY_MODE" --device "$DEVICE"; then + log ERROR "Connection test FAILED. Check test_connection.sh's logs." + exit 1 + fi + log OK "Connection test passed." + else + log WARN "test_connection.sh not found. Skipping automatic test." + fi + ;; + clear-deployment) + log INFO "Selected action: Clear deployment for $DEVICE using $DEPLOY_MODE" + if [ "$DEPLOY_MODE" = "k8s" ]; then + clear_k8s_deployment # Uses global $DEPLOYMENT_NS + elif [ "$DEPLOY_MODE" = "docker" ]; then + clear_docker_deployment "$DEVICE" + fi + log OK "Clearing deployment for $DEVICE finished." + ;; + clear-all) + log INFO "Selected action: Clear all ChatQnA K8s namespaces" + if [[ "$DEPLOY_MODE" != "k8s" ]]; then + log WARN "Clear-all is a K8s specific action. It will proceed assuming K8s context." + fi + clear_all_k8s_namespaces_dangerously + log OK "Clearing all K8s namespaces finished." + ;; + test) + section_header "Connection Test" + if [ -f "$SCRIPT_DIR_INSTALL/test_connection.sh" ]; then + if ! bash "$SCRIPT_DIR_INSTALL/test_connection.sh" --deploy-mode "$DEPLOY_MODE" --device "$DEVICE"; then + log ERROR "Connection test FAILED. Check test_connection.sh's logs." + exit 1 + fi + log OK "Connection test passed." + else + log WARN "test_connection.sh not found. Skipping automatic test." + fi + ;; + *) + log ERROR "Unknown action '$action'. This should not happen due to argument parsing." + usage + exit 1 + ;; +esac + +log OK "Script finished successfully for action: $action." +exit 0 \ No newline at end of file diff --git a/ChatQnA/deployment/one_click_chatqna.sh b/ChatQnA/deployment/one_click_chatqna.sh new file mode 100644 index 0000000000..9671926789 --- /dev/null +++ b/ChatQnA/deployment/one_click_chatqna.sh @@ -0,0 +1,312 @@ +#!/bin/bash +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# set -e # Removed set -e to allow user to decide to skip parts, script will handle errors +# set -o pipefail # Removed for same reason + +# Script's own directory +SCRIPT_DIR_ONECLICK=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${SCRIPT_DIR_ONECLICK}/utils.sh" # Assuming utils.sh is in the same directory +# LOG_FILE="one_click_chatqna.log" # Optional: specific log file +default_host_ip=$(hostname -I | awk '{print $1}' 2>/dev/null) + +# --- Default values --- +DEFAULT_DEVICE="xeon" +DEFAULT_TAG="latest" +REGISTRY="" +DEFAULT_REGISTRY="" +DEFAULT_DEPLOY_MODE="docker" +DEFAULT_CHECK_ENV_MODE="online" # For check_env.sh + +# --- Pre-requisite checks --- +if ! command_exists docker; then + log ERROR "Docker is not installed. Please install Docker first." + exit 1 +fi +# Kubectl check only if k8s mode is chosen later +# Git check if update_images needs it for cloning (it does) + +# --- Interactive Parameter Input --- +section_header "ChatQnA One-Click Interactive Setup" +log INFO "This script will guide you through deploying ChatQnA." +log INFO "Press Enter to use default values shown in [brackets]." + +DEFAULT_HUG_TOKEN=$(get_huggingface_token) + +if [ -n "$DEFAULT_HUG_TOKEN" ]; then + read -p "Found default Hugging Face Token. Use it? [Y/n]: " use_default + case "$use_default" in + [nN]|[nN][oO]) + HUG_TOKEN="" + ;; + *) + HUG_TOKEN="$DEFAULT_HUG_TOKEN" + ;; + esac +fi + +while [ -z "$HUG_TOKEN" ]; do + read -p "Enter Hugging Face Token (mandatory for model downloads): " HUG_TOKEN + if [ -z "$HUG_TOKEN" ]; then + log WARN "Hugging Face Token cannot be empty if models need to be downloaded." + fi +done + +log INFO "Using Hugging Face Token: ${HUG_TOKEN:0:4}...${HUG_TOKEN: -4}" +if [ ! -f $HOME/.cache/huggingface/token ]; then + log INFO 'Save Hugging Face Token into $HF_HOME/token' + echo $HUG_TOKEN > $HOME/.cache/huggingface/token +fi + +# Core Deployment Parameters +log INFO "--- Core Deployment Settings ---" +read -p "Enter DEVICE type (e.g., xeon, gaudi) [default: $DEFAULT_DEVICE]: " input_device +DEVICE="${input_device:-$DEFAULT_DEVICE}" +while [[ "$DEVICE" != "xeon" && "$DEVICE" != "gaudi" ]]; do + log WARN "Invalid DEVICE. Must be 'xeon' or 'gaudi'." + read -p "Enter DEVICE type (e.g., xeon, gaudi) [default: $DEFAULT_DEVICE]: " input_device + DEVICE="${input_device:-$DEFAULT_DEVICE}" +done + + +read -p "Enter Image TAG [default: $DEFAULT_TAG]: " input_tag +TAG="${input_tag:-$DEFAULT_TAG}" + +read -p "Enter Container REGISTRY (e.g., myregistry.com/myorg, leave blank for Docker Hub or local build defaults) [default: $DEFAULT_REGISTRY]: " input_registry +REGISTRY="${input_registry:-$DEFAULT_REGISTRY}" + +read -p "Enter DEPLOY_MODE (docker/k8s) [default: $DEFAULT_DEPLOY_MODE]: " input_deploy_mode +DEPLOY_MODE="${input_deploy_mode:-$DEFAULT_DEPLOY_MODE}" +while [[ "$DEPLOY_MODE" != "docker" && "$DEPLOY_MODE" != "k8s" ]]; do + log WARN "Invalid DEPLOY_MODE. Must be 'docker' or 'k8s'." + read -p "Enter DEPLOY_MODE (docker/k8s) [default: $DEFAULT_DEPLOY_MODE]: " input_deploy_mode + DEPLOY_MODE="${input_deploy_mode:-$DEFAULT_DEPLOY_MODE}" +done +echo + +# Proxy Parameters (Optional) +log INFO "--- Proxy Settings (optional, press Enter to skip or use system defaults) ---" +SYSTEM_HTTP_PROXY="${http_proxy:-${HTTP_PROXY:-}}" +SYSTEM_HTTPS_PROXY="${https_proxy:-${HTTPS_PROXY:-}}" +SYSTEM_NO_PROXY="${no_proxy:-${NO_PROXY:-}},$default_host_ip" + +prompt_http_proxy="Enter HTTP_PROXY" +[[ -n "$SYSTEM_HTTP_PROXY" ]] && prompt_http_proxy+=" [current/system: $SYSTEM_HTTP_PROXY]" +read -p "$prompt_http_proxy: " input_http_proxy +FINAL_HTTP_PROXY="${input_http_proxy:-$SYSTEM_HTTP_PROXY}" + +prompt_https_proxy="Enter HTTPS_PROXY" +[[ -n "$SYSTEM_HTTPS_PROXY" ]] && prompt_https_proxy+=" [current/system: $SYSTEM_HTTPS_PROXY]" +read -p "$prompt_https_proxy: " input_https_proxy +FINAL_HTTPS_PROXY="${input_https_proxy:-$SYSTEM_HTTPS_PROXY}" + +prompt_no_proxy="Enter NO_PROXY (comma-separated)" +[[ -n "$SYSTEM_NO_PROXY" ]] && prompt_no_proxy+=" [current/system: $SYSTEM_NO_PROXY]" +read -p "$prompt_no_proxy: " input_no_proxy +FINAL_NO_PROXY="${input_no_proxy:-$SYSTEM_NO_PROXY}" +echo + +# Model Configuration Parameters (Optional) +log INFO "--- Model Configuration (optional, press Enter to use defaults from sub-scripts) ---" +read -p "Enter Embedding Model ID (e.g., BAAI/bge-m3): " EMBED_MODEL +read -p "Enter Reranking Model ID (e.g., BAAI/bge-reranker-v2-m3): " RERANK_MODEL_ID +read -p "Enter LLM Model ID (e.g., Qwen/Qwen2-7B-Instruct): " LLM_MODEL_ID +read -p "Enter Data Mount Directory [default: ./data]: " MOUNT_DIR +echo + +section_header "Configuration Summary" +log INFO " Hugging Face Token: [REDACTED FOR SECURITY]" +log INFO " Device: $DEVICE" +log INFO " Image Tag: $TAG" +log INFO " Registry: ${REGISTRY:-Not set / Use sub-script default}" +log INFO " Deploy Mode: $DEPLOY_MODE" +log INFO " HTTP Proxy: ${FINAL_HTTP_PROXY:-Not set}" +log INFO " HTTPS Proxy: ${FINAL_HTTPS_PROXY:-Not set}" +log INFO " No Proxy: ${FINAL_NO_PROXY:-Not set}" +log INFO " Embedding Model: ${EMBED_MODEL:-Not set / Use sub-script default}" +log INFO " Reranking Model ID: ${RERANK_MODEL_ID:-Not set / Use sub-script default}" +log INFO " LLM Model ID: ${LLM_MODEL_ID:-Not set / Use sub-script default}" +log INFO " Mount Directory: ${MOUNT_DIR:-./data}" +echo + +read -p "Proceed with these settings? (Y/n): " confirm_settings +confirm_settings_lower=$(echo "${confirm_settings:-Y}" | tr '[:upper:]' '[:lower:]') +if [[ "$confirm_settings_lower" != "y" && "$confirm_settings_lower" != "yes" ]]; then + log INFO "Aborted by user." + exit 1 +fi +echo + +# --- Execution Steps --- + +# 1. Check environment +section_header "Step 1: Checking Environment (check_env.sh)" +read -p "Run environment check? (Y/n) [Default: Y]: " run_check_env +run_check_env_lower=$(echo "$run_check_env" | tr '[:upper:]' '[:lower:]') +if [[ "$run_check_env_lower" == "y" || "$run_check_env_lower" == "yes" || -z "$run_check_env" ]]; then + if [ -f "${SCRIPT_DIR_ONECLICK}/check_env.sh" ]; then + log INFO "Running check_env.sh --device $DEVICE --mode $DEFAULT_CHECK_ENV_MODE ..." + if ! bash "${SCRIPT_DIR_ONECLICK}/check_env.sh" --device "$DEVICE" --mode "$DEFAULT_CHECK_ENV_MODE"; then + log ERROR "Environment check failed. Please resolve issues or skip this step if you are sure." + read -p "Continue despite environment check failure? (y/N): " continue_anyway + if [[ "$(echo "$continue_anyway" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then + exit 1 + fi + else + log OK "Environment check passed." + fi + else + log WARN "check_env.sh not found. Skipping environment check." + fi +else + log INFO "Skipping environment check as per user choice." +fi +echo + +# 2. Build/Push Images (update_images.sh) +section_header "Step 2: Building and Pushing Images (update_images.sh)" +read -p "Build images with update_images.sh? (y/N) [Default: N]: " run_update_images +run_update_images_lower=$(echo "$run_update_images" | tr '[:upper:]' '[:lower:]') + +if [[ "$run_update_images_lower" == "y" || "$run_update_images_lower" == "yes" ]]; then + if [ ! -f "${SCRIPT_DIR_ONECLICK}/update_images.sh" ]; then + log ERROR "update_images.sh not found." + exit 1 + fi + + update_images_args=("--tag" "$TAG" "--build") # 默认包含 build + + # 询问是否需要 push + read -p "Push images to registry? (y/N) [Default: N]: " push_images + push_images_lower=$(echo "$push_images" | tr '[:upper:]' '[:lower:]') + if [[ "$push_images_lower" == "y" || "$push_images_lower" == "yes" ]]; then + update_images_args+=("--push") + # 检查 registry 是否设置 + if [ -z "$REGISTRY" ]; then + read -p "Setup local registry (localhost:5000)? (y/N) [Default: N]: " setup_local_reg + setup_local_reg_lower=$(echo "$setup_local_reg" | tr '[:upper:]' '[:lower:]') + if [[ "$setup_local_reg_lower" == "y" ]]; then + update_images_args+=("--setup-registry") + REGISTRY="localhost:5000" + else + log ERROR "Push requires registry. Use --registry to specify or --setup-registry for local." + exit 1 + fi + fi + fi + + # 处理 registry 参数 + if [ -n "$REGISTRY" ]; then + update_images_args+=("--registry" "$REGISTRY") + log INFO "Please ensure you are logged in to registry: $REGISTRY" + fi + + # 代理设置 + export http_proxy="${FINAL_HTTP_PROXY}" + export https_proxy="${FINAL_HTTPS_PROXY}" + export no_proxy="${FINAL_NO_PROXY}" + + log INFO "Running update_images.sh ${update_images_args[*]} ..." + if ! bash "${SCRIPT_DIR_ONECLICK}/update_images.sh" "${update_images_args[@]}"; then + log ERROR "Image build/push failed." + unset http_proxy https_proxy no_proxy + exit 1 + fi + unset http_proxy https_proxy no_proxy +else + log INFO "Skipping image update." +fi +echo + + +# 3. Set Helm/Manifest Values (set_values.sh) +# This step is important for both K8s and Docker (to prepare compose env files) +section_header "Step 3: Configuring Service Parameters (set_values.sh)" +if [ ! -f "${SCRIPT_DIR_ONECLICK}/set_values.sh" ]; then + log ERROR "set_values.sh not found. Cannot configure deployment parameters." + # Decide if this is fatal or skippable + read -p "Continue without running set_values.sh? (y/N): " skip_set_values + if [[ "$(echo "$skip_set_values" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then + exit 1 + fi +else + set_values_args=() + set_values_args+=("-d" "$DEVICE") + set_values_args+=("-m" "$DEPLOY_MODE") + set_values_args+=("-g" "$HUG_TOKEN") + set_values_args+=("-t" "$TAG") + + if [ -n "$REGISTRY" ]; then set_values_args+=("-r" "$REGISTRY"); fi + if [ -n "$FINAL_HTTP_PROXY" ]; then set_values_args+=("-p" "$FINAL_HTTP_PROXY"); fi + if [ -n "$FINAL_HTTPS_PROXY" ]; then set_values_args+=("-s" "$FINAL_HTTPS_PROXY"); fi # -s for set_values.sh + if [ -n "$FINAL_NO_PROXY" ]; then set_values_args+=("-n" "$FINAL_NO_PROXY"); fi # -n for set_values.sh additions + if [ -n "$EMBED_MODEL" ]; then set_values_args+=("-e" "$EMBED_MODEL"); fi + if [ -n "$RERANK_MODEL_ID" ]; then set_values_args+=("-a" "$RERANK_MODEL_ID"); fi # -a for set_values.sh + if [ -n "$LLM_MODEL_ID" ]; then set_values_args+=("-l" "$LLM_MODEL_ID"); fi + if [ -n "$MOUNT_DIR" ]; then set_values_args+=("-u" "$MOUNT_DIR"); fi + + + log INFO "Running set_values.sh ${set_values_args[*]} ..." + if ! bash "${SCRIPT_DIR_ONECLICK}/set_values.sh" "${set_values_args[@]}"; then + log ERROR "set_values.sh failed. Parameter configuration aborted." + exit 1 # This is usually critical + else + log OK "Parameter configuration (set_values.sh) finished successfully." + fi +fi +echo + +# 4. Install ChatQnA (install_chatqna.sh) +section_header "Step 4: Installing ChatQnA (install_chatqna.sh)" +if [ "$DEPLOY_MODE" == "k8s" ]; then + if ! command_exists kubectl; then + log ERROR "kubectl is not installed, but DEPLOY_MODE is k8s. Please install kubectl." + exit 1 + fi +fi + +if [ ! -f "${SCRIPT_DIR_ONECLICK}/install_chatqna.sh" ]; then + log ERROR "install_chatqna.sh not found. Cannot proceed with deployment." + exit 1 +fi + +install_args=() +install_args+=("--device" "$DEVICE") +install_args+=("--deploy-mode" "$DEPLOY_MODE") +install_args+=("--hf-token" "$HUG_TOKEN") +install_args+=("--tag" "$TAG") +install_args+=("--deploy") # Explicitly tell install_chatqna.sh to deploy + +if [ -n "$REGISTRY" ]; then install_args+=("--registry" "$REGISTRY"); fi +if [ -n "$FINAL_HTTP_PROXY" ]; then install_args+=("--http-proxy" "$FINAL_HTTP_PROXY"); fi +if [ -n "$FINAL_HTTPS_PROXY" ]; then install_args+=("--https-proxy" "$FINAL_HTTPS_PROXY"); fi +if [ -n "$FINAL_NO_PROXY" ]; then install_args+=("--no-proxy" "$FINAL_NO_PROXY"); fi +if [ -n "$EMBED_MODEL" ]; then install_args+=("--embed-model" "$EMBED_MODEL"); fi +if [ -n "$RERANK_MODEL_ID" ]; then install_args+=("--rerank-model" "$RERANK_MODEL_ID"); fi +if [ -n "$LLM_MODEL_ID" ]; then install_args+=("--llm-model" "$LLM_MODEL_ID"); fi +if [ -n "$MOUNT_DIR" ]; then install_args+=("--mount-dir" "$MOUNT_DIR"); fi + +log INFO "Running install_chatqna.sh ${install_args[*]} ..." +# The install_chatqna.sh script will also run a test at the end of its deploy action. +if ! bash "${SCRIPT_DIR_ONECLICK}/install_chatqna.sh" "${install_args[@]}"; then + log ERROR "install_chatqna.sh failed. Deployment process aborted." + exit 1 +else + log OK "ChatQnA installation and initial test (from install_chatqna.sh) process finished successfully." +fi +echo + +section_header "One-Click Setup Completed" +log OK "All selected steps executed. Please check logs for details." +if [ "$DEPLOY_MODE" == "docker" ]; then + log INFO "For Docker deployment, you can check running containers using: docker ps" + log INFO "To view logs for a service: docker logs " + log INFO "To stop services: Navigate to deployment/docker_compose/$DEVICE/ and run: docker compose down" +elif [ "$DEPLOY_MODE" == "k8s" ]; then + log INFO "For Kubernetes deployment, check pods status: kubectl get pods -n chatqna" # Assuming 'chatqna' namespace + log INFO "To view logs for a pod: kubectl logs -n chatqna" + log INFO "To delete the deployment: ./install_chatqna.sh --device $DEVICE --deploy-mode k8s --clear-deployment" +fi +local_ip_for_ui=$(hostname -I | awk '{print $1}' || echo "localhost") +log INFO "ChatQnA UI is accessible at: ${COLOR_OK}http://${local_ip_for_ui}${COLOR_RESET}" diff --git a/ChatQnA/deployment/set_values.sh b/ChatQnA/deployment/set_values.sh new file mode 100644 index 0000000000..c564a51581 --- /dev/null +++ b/ChatQnA/deployment/set_values.sh @@ -0,0 +1,320 @@ +#!/bin/bash +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +SCRIPT_DIR_SETVALUES=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${SCRIPT_DIR_SETVALUES}/utils.sh" # Assuming utils.sh is in the same directory +# LOG_FILE="set_values.log" # Optional: specific log file + +REPO_DIR=$(realpath "$SCRIPT_DIR_SETVALUES/../") + +# Default values +DEVICE="xeon" # Mandatory, e.g., xeon, gaudi +DEPLOY_MODE="docker" # Default deployment mode +REGISTRY="" # Renamed from REPOSITORY for consistency with other scripts' params +TAG="latest" +EMBED_MODEL="" +RERANK_MODEL_ID="" +LLM_MODEL_ID="" +HUGGINGFACEHUB_API_TOKEN="" +HTTP_PROXY="" +HTTPS_PROXY="" +NO_PROXY_BASE="cluster.local,localhost,127.0.0.1,chatqna-xeon-ui-server,chatqna-xeon-backend-server,dataprep-service,mosec-embedding-service,embedding,retriever,reranking,mosec-reranking-service,vllm-service" # Added localhost, 127.0.0.1 +NO_PROXY="$NO_PROXY_BASE" +MOUNT_DIR="" +# Removed ip_address from here, will be determined in update_docker_config if needed or use localhost/service_name + +# Function to display usage information +usage() { + echo "Usage: $0 -d [OPTIONS]" + echo " -d (Required) Target device/platform (e.g., xeon, gaudi)." + echo " -m Deployment Mode (e.g., k8s, docker, default: docker)." + echo " -r Container registry/prefix (e.g., myregistry.com/myorg)." + echo " -t Image tag (default: latest)." + echo " -u Data mount directory for Docker volumes (default: ./data)." + echo " -e Embedding model name/path." + echo " -a Reranking model ID." + echo " -l LLM model ID." + echo " -g HuggingFaceHub API token." + echo " -p HTTP proxy URL." + echo " -s HTTPS proxy URL." + echo " -n Comma-separated values to add to NO_PROXY (in addition to defaults)." + echo " -h Display this help message." + exit 1 +} + +if [ $# -eq 0 ]; then + log ERROR "No parameters passed. Use -h for help." + usage # usage already exits +fi + +# Parse command-line arguments +while getopts "d:m:r:t:e:a:l:g:p:s:n:u:h" opt; do + case $opt in + d) DEVICE="$OPTARG" ;; + m) DEPLOY_MODE="$OPTARG" ;; + r) REGISTRY="$OPTARG" ;; # Changed from REPOSITORY + t) TAG="$OPTARG" ;; + e) EMBED_MODEL="$OPTARG" ;; + a) RERANK_MODEL_ID="$OPTARG" ;; + l) LLM_MODEL_ID="$OPTARG" ;; + g) HUGGINGFACEHUB_API_TOKEN="$OPTARG" ;; + p) HTTP_PROXY="$OPTARG" ;; + s) HTTPS_PROXY="$OPTARG" ;; + n) NO_PROXY_EXTRA="$OPTARG" ;; + u) MOUNT_DIR="$OPTARG" ;; + h) usage ;; + *) usage ;; + esac +done + +# Validate required DEVICE parameter +if [ -z "$DEVICE" ]; then + log ERROR "-d is a required parameter." + usage +fi +if [[ "$DEVICE" != "xeon" && "$DEVICE" != "gaudi" ]]; then + log ERROR "DEVICE must be either 'xeon' or 'gaudi'. Received: '$DEVICE'" + usage +fi +if [[ "$DEPLOY_MODE" != "k8s" && "$DEPLOY_MODE" != "docker" ]]; then + log ERROR "DEPLOY_MODE must be either 'k8s' or 'docker'. Received: '$DEPLOY_MODE'" + usage +fi + +# Append extra NO_PROXY values if provided +if [ -n "$NO_PROXY_EXTRA" ]; then + NO_PROXY="$NO_PROXY_BASE,$NO_PROXY_EXTRA" +fi + +section_header "Applying Configuration Values" +log INFO "Effective configuration values:" +log INFO " DEVICE: $DEVICE" +log INFO " DEPLOY_MODE: $DEPLOY_MODE" +log INFO " REGISTRY: ${REGISTRY:-Not set, defaults will apply in target files}" +log INFO " TAG: $TAG" +log INFO " MOUNT_DIR: ${MOUNT_DIR:-Not set, default to ./data}" +log INFO " EMBED_MODEL: ${EMBED_MODEL:-Not set, service default will apply}" +log INFO " RERANK_MODEL_ID: ${RERANK_MODEL_ID:-Not set, service default will apply}" +log INFO " LLM_MODEL_ID: ${LLM_MODEL_ID:-Not set, service default will apply}" +log INFO " HUGGINGFACEHUB_API_TOKEN: ${HUGGINGFACEHUB_API_TOKEN:+(set, not shown for security)}" +log INFO " HTTP_PROXY: ${HTTP_PROXY:-Not set}" +log INFO " HTTPS_PROXY: ${HTTPS_PROXY:-Not set}" +log INFO " NO_PROXY: $NO_PROXY" + +update_yaml_image() { + local file_path="$1" + local image_base_name="$2" # e.g., "opea/aisolution/chatqna" + local new_registry="$3" + local new_tag="$4" + + # Construct the original image pattern carefully. Allows for an optional existing registry. + # Example: image: opea/aisolution/chatqna:latest + # Example: image: my.old.registry/opea/aisolution/chatqna:some-tag + # This sed tries to replace the registry and tag for a known image base name. + + local new_image_prefix="" + if [ -n "$new_registry" ]; then + new_image_prefix="${new_registry}/" + fi + + # sed -i "s|image: \(.*\/\)*${image_base_name}:.*|image: ${new_image_prefix}${image_base_name}:${new_tag}|g" "$file_path" + # A simpler approach if `opea/aisolution` is a fixed part that might be *prefixed* or *replaced* by REGISTRY + # If REGISTRY is a full prefix like "my.registry/myorg", and images are "opea/aisolution/component" + # The new image would be "my.registry/myorg/opea/aisolution/component:new_tag" + # Or if REGISTRY is "my.registry/myorg" and it *replaces* "opea", then "my.registry/myorg/aisolution/component:new_tag" + + # Assuming REGISTRY replaces "opea/aisolution" part or prefixes it. + # Let's assume images are like `opea/aisolution/image-name` and `REGISTRY` is `new-registry/new-org` + # then final image is `new-registry/new-org/image-name:TAG` + # This is complex to do reliably with sed for all YAML structures. + # A common pattern is that the default image is 'opea/aisolution/some-service:latest' + # and we want to change it to '$REGISTRY/some-service:$TAG' if REGISTRY is 'myhub.com/myteam' + # OR to '$REGISTRY/opea/aisolution/some-service:$TAG' if REGISTRY is just 'myhub.com' + + # Let's simplify: if REGISTRY is set, it replaces the part before the specific service name. + # If original is "image: opea/aisolution/chatqna:latest" + # If REGISTRY="my.repo", TAG="v2", then "image: my.repo/chatqna:v2" (assuming image_base_name includes "opea/aisolution") + # This needs a clear convention on what REGISTRY means and what image_base_name is. + + # The find command below is more general but still relies on a common original prefix. + # `find . -name '*.yaml' -type f -exec sed -i -E -e "s|image: *[^ /]+/aisolution/([^:]+):.+|image: ${REGISTRY}/\1:${TAG}|g" {} \;` + # This is too specific. + # The original `sed -i -e "/image: /s#opea/aisolution#${REGISTRY}#g" -e "/image: /s#latest#${TAG}#g" {} \;` + # assumes REGISTRY replaces "opea/aisolution". This is a strong assumption. + # A safer way for tags: + # sed -i -E "s|(image: .*:)[^:]+$|\1${new_tag}|g" "$file_path" + # And for registry, if REGISTRY is a full prefix for the image name (e.g., image_name is "chatqna", registry is "myorg"): + # sed -i -E "s|image: *([^/]+)/([^:]+):|image: ${new_registry}/\2:|g" "$file_path" + # This is very tricky. The existing find command in `update_k8s_config` is probably the most practical if assumptions hold. + log WARN "YAML image update logic is complex and highly dependent on current YAML structure and REGISTRY definition. Review carefully." +} + + +function update_k8s_config() { + local k8s_manifests_base_path="${REPO_DIR}/deployment/kubernetes" + local device_manifest_dir="${k8s_manifests_base_path}/${DEVICE}" + local configmap_file_pattern="${device_manifest_dir}/chatQnA_config_map.yaml" # Assuming this naming + + # Try to find a configmap, could be named differently + local configmap_file + configmap_file=$(find "$device_manifest_dir" -name "*config*.yaml" -type f -print -quit) + + + if [ ! -f "$configmap_file" ]; then + log WARN "K8s ConfigMap file not found in '$device_manifest_dir' (searched for *config*.yaml). Skipping K8s model/token config updates." + else + log INFO "Updating K8s ConfigMap: $configmap_file" + # Use yq if available for robust YAML editing, otherwise sed + local use_yq=false + if command_exists yq; then use_yq=true; fi + + if [ -n "$EMBED_MODEL" ]; then + log INFO " Updating EMBED_MODEL to: $EMBED_MODEL" + if $use_yq; then yq e ".data.EMBED_MODEL = \"$EMBED_MODEL\"" -i "$configmap_file"; else \ + sed -i "s|^\([[:space:]]*EMBED_MODEL:\).*|\1 \"$EMBED_MODEL\"|" "$configmap_file"; fi + fi + if [ -n "$RERANK_MODEL_ID" ]; then + log INFO " Updating RERANK_MODEL_ID to: $RERANK_MODEL_ID" + if $use_yq; then yq e ".data.RERANK_MODEL_ID = \"$RERANK_MODEL_ID\"" -i "$configmap_file"; else \ + sed -i "s|^\([[:space:]]*RERANK_MODEL_ID:\).*|\1 \"$RERANK_MODEL_ID\"|" "$configmap_file"; fi + fi + if [ -n "$LLM_MODEL_ID" ]; then + log INFO " Updating LLM_MODEL_ID to: $LLM_MODEL_ID" + if $use_yq; then yq e ".data.LLM_MODEL_ID = \"$LLM_MODEL_ID\"" -i "$configmap_file"; else \ + sed -i "s|^\([[:space:]]*LLM_MODEL_ID:\).*|\1 \"$LLM_MODEL_ID\"|" "$configmap_file"; fi + fi + if [ -n "$HUGGINGFACEHUB_API_TOKEN" ]; then + log INFO " Updating HUGGINGFACEHUB_API_TOKEN..." + if $use_yq; then + yq e ".data.HUGGINGFACEHUB_API_TOKEN = \"$HUGGINGFACEHUB_API_TOKEN\"" -i "$configmap_file" + yq e ".data.HF_TOKEN = \"$HUGGINGFACEHUB_API_TOKEN\"" -i "$configmap_file" # If HF_TOKEN is also used + else + sed -i "s|^\([[:space:]]*HUGGINGFACEHUB_API_TOKEN:\).*|\1 \"$HUGGINGFACEHUB_API_TOKEN\"|" "$configmap_file" + sed -i "s|^\([[:space:]]*HF_TOKEN:\).*|\1 \"$HUGGINGFACEHUB_API_TOKEN\"|" "$configmap_file" + fi + fi + # Add proxy settings to ConfigMap if they are defined in it + if [ -n "$HTTP_PROXY" ]; then + log INFO " Updating HTTP_PROXY..." + if $use_yq; then yq e ".data.HTTP_PROXY = \"$HTTP_PROXY\"" -i "$configmap_file"; else \ + sed -i "s|^\([[:space:]]*HTTP_PROXY:\).*|\1 \"$HTTP_PROXY\"|" "$configmap_file"; fi + fi + if [ -n "$HTTPS_PROXY" ]; then + log INFO " Updating HTTPS_PROXY..." + if $use_yq; then yq e ".data.HTTPS_PROXY = \"$HTTPS_PROXY\"" -i "$configmap_file"; else \ + sed -i "s|^\([[:space:]]*HTTPS_PROXY:\).*|\1 \"$HTTPS_PROXY\"|" "$configmap_file"; fi + fi + if [ -n "$NO_PROXY" ]; then + log INFO " Updating NO_PROXY..." + if $use_yq; then yq e ".data.NO_PROXY = \"$NO_PROXY\"" -i "$configmap_file"; else \ + sed -i "s|^\([[:space:]]*NO_PROXY:\).*|\1 \"$NO_PROXY\"|" "$configmap_file"; fi + fi + fi + + if [ -d "$device_manifest_dir" ]; then + if [ -n "$REGISTRY" ] || [ -n "$TAG" ]; then + log INFO "Updating images in K8s manifests under $device_manifest_dir:" + [ -n "$REGISTRY" ] && log INFO " Setting registry part to: $REGISTRY" + [ -n "$TAG" ] && log INFO " Setting tag to: $TAG" + + # This find and sed command is quite aggressive. It assumes all images under "opea/aisolution/" + # should be replaced by "REGISTRY/" and tag updated. + # Example: image: opea/aisolution/foo:latest -> image: my.reg/foo:newtag + # This will break if images are not from "opea/aisolution" or if REGISTRY is not just a prefix. + # Consider making this more targeted or using kustomize/helm for image overrides. + find "$device_manifest_dir" -name '*.yaml' -type f -print0 | while IFS= read -r -d $'\0' file; do + log INFO " Processing $file for image updates..." + if [ -n "$REGISTRY" ]; then + # This sed replaces 'opea/aisolution' with the new registry. + # It assumes 'opea/aisolution' is a common prefix for images that need changing. + # e.g. image: opea/aisolution/my-service:some-tag -> image: ${REGISTRY}/my-service:some-tag + sed -i -E "s|image: *([A-Za-z0-9.-]+/)*opea/aisolution/([^:]+):(.*)|image: ${REGISTRY}/\2:\3|g" "$file" + fi + if [ -n "$TAG" ]; then + # This sed changes the tag of any image line. + # e.g. image: something/another:latest -> image: something/another:${TAG} + sed -i -E "s|(image: *[^:]+:)[^:]+$|\1${TAG}|g" "$file" + fi + done + fi + else + log WARN "K8s manifest directory for device '$DEVICE' ($device_manifest_dir) not found. Skipping image updates in K8s YAMLs." + fi +} + +function update_docker_config() { + log INFO "Preparing Docker environment variables for device: $DEVICE" + local target_compose_dir="${REPO_DIR}/deployment/docker_compose/${DEVICE}" + local set_env_script_path="${target_compose_dir}/set_env.sh" # Original script + + # Prefer .env file if it exists, otherwise modify set_env.sh + # For Docker, it's usually better to set these in a .env file read by docker-compose + # or directly in the docker-compose.yaml environment sections. + # Modifying set_env.sh directly is also an option if that's the established workflow. + + # For this review, I'll stick to exporting, assuming set_env.sh will pick them up if sourced by compose, + # or that compose file directly uses these exported vars. + # The original script just exported them, which means they need to be available when `docker compose` runs. + # This script (`set_values.sh`) is typically sourced or its output is eval'd by the caller. + # If this script is run standalone, exports won't persist for a later `docker compose` call. + # The original `install_chatqna.sh` sources `set_env.sh` from the compose dir. + # The original `one_click_chatqna.sh` calls this `set_values.sh` then `install_chatqna.sh`. + # The crucial part is that `docker_compose/${DEVICE}/set_env.sh` should correctly use these. + + # Let's assume `docker_compose/${DEVICE}/set_env.sh` is the primary place to set these for Docker. + # We will modify that script. + if [ ! -f "$set_env_script_path" ]; then + log WARN "Docker set_env.sh script not found at '$set_env_script_path'. Skipping Docker config updates." + return + fi + log INFO "Updating Docker environment script: $set_env_script_path" + + update_line_in_file() { + local file="$1" + local key="$2" + local value="$3" + # If key exists, update it. If not, append it. + # Ensure value is quoted if it contains spaces or special chars, though for env vars it's usually fine. + if grep -q "export ${key}=" "$file"; then + sed -i "s|^export ${key}=.*|export ${key}=${value}|" "$file" + else + echo "export ${key}=${value}" >> "$file" + fi + } + + # It's better if set_env.sh sources another file that we can cleanly overwrite, + # or if set_env.sh is designed to use defaults if overrides aren't present. + # For now, directly editing set_env.sh based on parameters. + if [ -n "$REGISTRY" ]; then update_line_in_file "$set_env_script_path" "REGISTRY" "$REGISTRY"; fi # Assuming set_env.sh uses REPOSITORY/REGISTRY + if [ -n "$TAG" ]; then update_line_in_file "$set_env_script_path" "IMAGE_TAG" "$TAG"; fi + + if [ -n "$HTTP_PROXY" ]; then update_line_in_file "$set_env_script_path" "HTTP_PROXY" "$HTTP_PROXY"; fi + if [ -n "$HTTPS_PROXY" ]; then update_line_in_file "$set_env_script_path" "HTTPS_PROXY" "$HTTPS_PROXY"; fi + if [ -n "$NO_PROXY" ]; then update_line_in_file "$set_env_script_path" "NO_PROXY" "\"$NO_PROXY\""; fi # Quote if it has commas + [ -n "$MOUNT_DIR" ] && update_line_in_file "$set_env_script_path" "MOUNT_DIR" "$MOUNT_DIR" + + [ -n "$EMBED_MODEL" ] && export MOSEC_EMBEDDING_MODEL_ID=$EMBED_MODEL + [ -n "$RERANK_MODEL_ID" ] && export MOSEC_RERANKING_MODEL_ID=$RERANK_MODEL_ID + [ -n "$LLM_MODEL_ID" ] && export LLM_MODEL_ID=$LLM_MODEL_ID + [ -n "$HUGGINGFACEHUB_API_TOKEN" ] && export HUGGINGFACEHUB_API_TOKEN=$HUGGINGFACEHUB_API_TOKEN + + log INFO "Docker configurations prepared/updated in $set_env_script_path." + # The original script EXPORTED variables. This means the calling shell (e.g. one_click_chatqna) + # would need to `source` this script for those exports to be effective for subsequent `docker compose` calls. + # Alternatively, `docker compose` can use an `.env` file. + # If `install_chatqna.sh` sources the `docker_compose/$DEVICE/set_env.sh`, then modifying that file is correct. +} + + +# Main logic based on DEPLOY_MODE +if [ "$DEPLOY_MODE" = "k8s" ]; then + update_k8s_config +elif [ "$DEPLOY_MODE" = "docker" ]; then + update_docker_config +else + log ERROR "Unsupported DEPLOY_MODE '$DEPLOY_MODE'. This check should have been caught earlier." + exit 1 +fi + +log OK "Configuration update process completed for DEVICE '$DEVICE' in DEPLOY_MODE '$DEPLOY_MODE'." \ No newline at end of file diff --git a/ChatQnA/deployment/test_connection.sh b/ChatQnA/deployment/test_connection.sh new file mode 100644 index 0000000000..9e61781fa2 --- /dev/null +++ b/ChatQnA/deployment/test_connection.sh @@ -0,0 +1,494 @@ +#!/bin/bash +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# set -e +# set -o pipefail + +# Script's own directory +SCRIPT_DIR_TEST=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${SCRIPT_DIR_TEST}/utils.sh" + +# Default values +DEPLOY_MODE="docker" +K8S_NAMESPACE="chatqna" +DEVICE="xeon" +LOG_FILE="test_connection.log" # Ensure log file is defined + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --deploy-mode) + DEPLOY_MODE="$2" + shift 2 + ;; + --k8s-namespace) + K8S_NAMESPACE="$2" + shift 2 + ;; + --device) + DEVICE="$2" + shift 2 + ;; + *) + log WARN "Unknown option in test_connection.sh: $1" + shift + ;; + esac +done + +section_header "ChatQnA Connection Test" +log INFO "Mode: $DEPLOY_MODE, Device: $DEVICE, K8s Namespace: $K8S_NAMESPACE (if applicable)" + +# --- Helper functions --- +check_k8s_pods_ready() { + if ! kubectl get pods -n "$K8S_NAMESPACE" --no-headers -o custom-columns="NAME:.metadata.name,READY:.status.conditions[?(@.type=='Ready')].status" 2>/dev/null | grep -v "True$" >/dev/null; then + log OK "All pods in $K8S_NAMESPACE seem to be ready." + return 0 + else + log WARN "Not all pods in $K8S_NAMESPACE are ready. Test might fail." + kubectl get pods -n "$K8S_NAMESPACE" --no-headers -o custom-columns="NAME:.metadata.name,READY:.status.conditions[?(@.type=='Ready')].status,STATUS:.status.phase" | grep -v "True[[:space:]]*Running" >> "$LOG_FILE" + return 1 + fi +} + +get_k8s_service_endpoint() { + local service_name="$1" + local namespace="$2" + local ip_address port + + ip_address=$(kubectl get svc "$service_name" -n "$namespace" -o jsonpath='{.spec.clusterIP}' 2>/dev/null) + if [ -z "$ip_address" ] || [[ "$ip_address" == "None" ]]; then + log WARN "Could not get ClusterIP for service $service_name in $namespace. Trying NodePort or Ingress if applicable." + # Add NodePort/Ingress detection here if needed + return 1 + fi + port=$(kubectl get svc "$service_name" -n "$namespace" -o jsonpath='{.spec.ports[0].port}' 2>/dev/null) + if [ -z "$port" ]; then + log ERROR "Could not get port for service $service_name in $namespace." + return 1 + fi + echo "$ip_address:$port" +} + +get_host_ip_for_docker() { + local found_ip + found_ip=$(hostname -I | awk '{print $1}' 2>/dev/null) + if [ -z "$found_ip" ]; then + found_ip=$(ip route get 1.1.1.1 | awk '{print $7; exit}' 2>/dev/null) + fi + export no_proxy="$no_proxy,$found_ip" + echo "${found_ip:-127.0.0.1}" +} + +# --- Sub-service Validation Functions --- +MAX_RETRIES=3 +RETRY_DELAY=5 +failed_services=() # Array to store names of failed services + +# Function to validate a sub-service in K8s +validate_k8s_sub_service() { + local URL="$1" + local EXPECTED_RESULT="$2" + local SERVICE_NAME="$3" + local INPUT_DATA="$4" # Optional: Payload for POST + local CURL_OPTS="$5" # Optional: Extra curl options (e.g., for file upload) + + local ATTEMPT=0 + local HTTP_STATUS=0 + local RESPONSE_BODY="" + local TEMP_RESPONSE_FILE="/tmp/sub_svc_resp_$(date +%s%N)" # Unique temp file inside pod + + while [ $ATTEMPT -lt $MAX_RETRIES ]; do + ATTEMPT=$((ATTEMPT + 1)) + log INFO "[Attempt $ATTEMPT/$MAX_RETRIES] Validating $SERVICE_NAME at $URL" + + local curl_cmd="curl --max-time 30 --silent --write-out 'HTTPSTATUS:%{http_code}' -o ${TEMP_RESPONSE_FILE} -X POST ${CURL_OPTS} -H 'Content-Type: application/json'" + # Adjust Content-Type and method for specific cases like dataprep upload + if [[ "$SERVICE_NAME" == *"dataprep_upload"* ]]; then + curl_cmd="curl --max-time 60 --silent --write-out 'HTTPSTATUS:%{http_code}' -o ${TEMP_RESPONSE_FILE} -X POST ${CURL_OPTS}" # Content-type is set by -F + elif [[ "$SERVICE_NAME" == *"dataprep_get"* || "$SERVICE_NAME" == *"dataprep_del"* ]]; then + curl_cmd="curl --max-time 30 --silent --write-out 'HTTPSTATUS:%{http_code}' -o ${TEMP_RESPONSE_FILE} -X POST -H 'Content-Type: application/json'" + if [ -n "$INPUT_DATA" ]; then + curl_cmd="$curl_cmd -d '$INPUT_DATA'" + fi + elif [ -n "$INPUT_DATA" ]; then + curl_cmd="$curl_cmd -d '$INPUT_DATA'" + fi + curl_cmd="$curl_cmd '$URL'" + + local full_cmd="sh -c \"$curl_cmd\"" + local HTTP_RESPONSE + HTTP_RESPONSE=$(kubectl exec "$CLIENT_POD_NAME" -n "$K8S_NAMESPACE" -- $full_cmd 2>>"$LOG_FILE") + local kubectl_exit_code=$? + + if [[ $kubectl_exit_code -ne 0 ]]; then + log WARN "[$SERVICE_NAME] kubectl exec failed (Exit Code: $kubectl_exit_code). Retrying in $RETRY_DELAY seconds..." + sleep $RETRY_DELAY + continue + fi + + HTTP_STATUS=$(echo "$HTTP_RESPONSE" | sed -e 's/.*HTTPSTATUS://') + # Retrieve body from pod's temp file + RESPONSE_BODY=$(kubectl exec "$CLIENT_POD_NAME" -n "$K8S_NAMESPACE" -- cat "$TEMP_RESPONSE_FILE" 2>/dev/null || echo "Failed to retrieve response body") + # Clean up temp file inside pod + kubectl exec "$CLIENT_POD_NAME" -n "$K8S_NAMESPACE" -- rm -f "$TEMP_RESPONSE_FILE" &> /dev/null + + if [[ "$HTTP_STATUS" == "200" ]]; then + log INFO "[$SERVICE_NAME] Received HTTP 200." + if [[ -n "$EXPECTED_RESULT" ]] && ! echo "$RESPONSE_BODY" | grep -q "$EXPECTED_RESULT"; then + log WARN "[$SERVICE_NAME] HTTP 200 received, but response body does not contain expected result '$EXPECTED_RESULT'." + log WARN "[$SERVICE_NAME] Response body: $(echo "$RESPONSE_BODY" | head -c 200)" # Log snippet + # Decide if this is a failure or just a warning - for now, treat as success if 200 + log OK "[$SERVICE_NAME] Validation check passed (HTTP 200)." + return 0 # Success + else + log OK "[$SERVICE_NAME] Validation check passed (HTTP 200 and expected content found)." + return 0 # Success + fi + else + log WARN "[$SERVICE_NAME] Request failed with status $HTTP_STATUS. Response: $(echo "$RESPONSE_BODY" | head -c 200)" + if [ $ATTEMPT -lt $MAX_RETRIES ]; then + log INFO "Retrying in $RETRY_DELAY seconds..." + sleep $RETRY_DELAY + fi + fi + done + + log ERROR "[$SERVICE_NAME] Validation failed after $MAX_RETRIES attempts. Last status: $HTTP_STATUS." + failed_services+=("$SERVICE_NAME") + return 1 +} + +# Function to validate a sub-service in Docker +validate_docker_sub_service() { + local URL="$1" + local EXPECTED_RESULT="$2" + local SERVICE_NAME="$3" + local INPUT_DATA="$4" # Optional: Payload for POST + local CURL_OPTS="$5" # Optional: Extra curl options + + local ATTEMPT=0 + local HTTP_STATUS=0 + local RESPONSE_BODY_FILE=$(mktemp) + + while [ $ATTEMPT -lt $MAX_RETRIES ]; do + ATTEMPT=$((ATTEMPT + 1)) + log INFO "[Attempt $ATTEMPT/$MAX_RETRIES] Validating $SERVICE_NAME at $URL" + + local curl_cmd_base="curl --max-time 30 --silent --write-out 'HTTPSTATUS:%{http_code}' -o ${RESPONSE_BODY_FILE} -X POST ${CURL_OPTS}" + # Adjust Content-Type and method for specific cases + if [[ "$SERVICE_NAME" == *"dataprep_upload"* ]]; then + curl_cmd_base="curl --max-time 60 --silent --write-out 'HTTPSTATUS:%{http_code}' -o ${RESPONSE_BODY_FILE} -X POST ${CURL_OPTS}" # Content-type is set by -F + elif [[ "$SERVICE_NAME" == *"dataprep_get"* || "$SERVICE_NAME" == *"dataprep_del"* ]]; then + curl_cmd_base="$curl_cmd_base -H 'Content-Type: application/json'" + if [ -n "$INPUT_DATA" ]; then + curl_cmd_base="$curl_cmd_base -d '$INPUT_DATA'" + fi + elif [ -n "$INPUT_DATA" ]; then + curl_cmd_base="$curl_cmd_base -H 'Content-Type: application/json' -d '$INPUT_DATA'" + fi + curl_cmd_base="$curl_cmd_base '$URL'" + + local HTTP_RESPONSE + HTTP_RESPONSE=$(eval "$curl_cmd_base" 2>>"$LOG_FILE") # Use eval to handle quotes in variables correctly + local curl_exit_code=$? + + if [[ $curl_exit_code -ne 0 ]]; then + # Curl exit codes: https://everything.curl.dev/usingcurl/returns + log WARN "[$SERVICE_NAME] Curl command failed (Exit Code: $curl_exit_code). Retrying in $RETRY_DELAY seconds..." + sleep $RETRY_DELAY + continue + fi + + HTTP_STATUS=$(echo "$HTTP_RESPONSE" | sed -e 's/.*HTTPSTATUS://') + local RESPONSE_BODY + RESPONSE_BODY=$(cat "$RESPONSE_BODY_FILE") + + if [[ "$HTTP_STATUS" == "200" ]]; then + log INFO "[$SERVICE_NAME] Received HTTP 200." + if [[ -n "$EXPECTED_RESULT" ]] && ! echo "$RESPONSE_BODY" | grep -q "$EXPECTED_RESULT"; then + log WARN "[$SERVICE_NAME] HTTP 200 received, but response body does not contain expected result '$EXPECTED_RESULT'." + log WARN "[$SERVICE_NAME] Response body: $(echo "$RESPONSE_BODY" | head -c 200)" + log OK "[$SERVICE_NAME] Validation check passed (HTTP 200)." + rm -f "$RESPONSE_BODY_FILE" + return 0 # Success + else + log OK "[$SERVICE_NAME] Validation check passed (HTTP 200 and expected content found)." + rm -f "$RESPONSE_BODY_FILE" + return 0 # Success + fi + else + log WARN "[$SERVICE_NAME] Request failed with status $HTTP_STATUS. Response: $(echo "$RESPONSE_BODY" | head -c 200)" + if [ $ATTEMPT -lt $MAX_RETRIES ]; then + log INFO "Retrying in $RETRY_DELAY seconds..." + sleep $RETRY_DELAY + fi + fi + done + + log ERROR "[$SERVICE_NAME] Validation failed after $MAX_RETRIES attempts. Last status: $HTTP_STATUS." + failed_services+=("$SERVICE_NAME") + rm -f "$RESPONSE_BODY_FILE" + return 1 +} + +# Function to run all sub-service tests +run_sub_service_tests() { + section_header "Running Sub-Service Validation" + failed_services=() # Reset failures + + local dataprep_file_content="Deep learning is a subset of machine learning..." + local temp_dataprep_file="" + + if [[ "$DEPLOY_MODE" == "k8s" ]]; then + # --- K8s Sub-service Tests --- + local dataprep_svc="dataprep-svc" + local embedding_svc="embedding-mosec-svc" # Check actual service name + local retriever_svc="retriever-svc" + local reranking_svc="reranking-mosec-svc" # Check actual service name + local llm_svc="llm-dependency-svc" # Check actual service name + + local dataprep_ep=$(get_k8s_service_endpoint "$dataprep_svc" "$K8S_NAMESPACE") + local embedding_ep=$(get_k8s_service_endpoint "$embedding_svc" "$K8S_NAMESPACE") + local retriever_ep=$(get_k8s_service_endpoint "$retriever_svc" "$K8S_NAMESPACE") + local reranking_ep=$(get_k8s_service_endpoint "$reranking_svc" "$K8S_NAMESPACE") + local llm_ep=$(get_k8s_service_endpoint "$llm_svc" "$K8S_NAMESPACE") + + # Create dataprep file inside pod + local pod_dataprep_file="/tmp/dataprep_file.txt" + log INFO "Creating dataprep test file in pod $CLIENT_POD_NAME..." + if ! echo "$dataprep_file_content" | kubectl exec -i "$CLIENT_POD_NAME" -n "$K8S_NAMESPACE" -- tee "$pod_dataprep_file" > /dev/null; then + log ERROR "Failed to create dataprep file in pod. Skipping dataprep upload test." + else + validate_k8s_sub_service \ + "http://${dataprep_ep}/v1/dataprep/ingest" \ + "Data preparation succeeded" \ + "k8s-dataprep_upload_file" \ + "" \ + "-F 'files=@${pod_dataprep_file}' -H 'Content-Type: multipart/form-data'" # Curl opts for file upload + # Clean up file in pod + kubectl exec "$CLIENT_POD_NAME" -n "$K8S_NAMESPACE" -- rm -f "$pod_dataprep_file" &> /dev/null + fi + + validate_k8s_sub_service \ + "http://${embedding_ep}/v1/embeddings" \ + "embedding" \ + "k8s-embedding" \ + '{"input":"What is Deep Learning?"}' + + local test_embedding # Generate or use a fixed embedding + test_embedding=$(python3 -c "import random; embedding = [random.uniform(-1, 1) for _ in range(1024)]; print(embedding)" 2>/dev/null || echo "[0.1, -0.2, ...]") # Fallback if python fails + validate_k8s_sub_service \ + "http://${retriever_ep}/v1/retrieval" \ + "retrieved_docs" \ + "k8s-retrieval" \ + "{\"text\":\"What is deep learning?\",\"embedding\":${test_embedding}}" + + validate_k8s_sub_service \ + "http://${reranking_ep}/v1/reranking" \ + '"score":' \ + "k8s-reranking" \ + '{"initial_query":"What is Deep Learning?", "retrieved_docs": [{"text":"Deep Learning is not..."}, {"text":"Deep learning is..."}], "top_n":2}' # Adjusted expected result based on common reranker output + + # Note: K8s reference used /generate, check actual API + validate_k8s_sub_service \ + "http://${llm_ep}/generate" \ + "generated_text" \ + "k8s-llm" \ + '{"inputs":"What is Deep Learning?","parameters":{"max_new_tokens":17, "do_sample": true}}' + + # Optional: Dataprep Delete test + validate_k8s_sub_service \ + "http://${dataprep_ep}/v1/dataprep/delete" \ + '"status":true' \ + "k8s-dataprep_del" \ + '{"file_path": "all"}' + + elif [[ "$DEPLOY_MODE" == "docker" ]]; then + # --- Docker Sub-service Tests --- + local HOST_IP_DOCKER=$(get_host_ip_for_docker) # Reuse host IP determined earlier + # Ports based on the provided compose file's external ports + local dataprep_port="11101" + local embedding_port="6000" + local retriever_port="7000" + local reranking_port="8000" + local llm_port="9009" # vllm-service external port + + # Create local dataprep file + temp_dataprep_file=/tmp/dataprep_file.txt + log INFO "Creating local dataprep test file at $temp_dataprep_file" + echo "$dataprep_file_content" > "$temp_dataprep_file" + + validate_docker_sub_service \ + "http://${HOST_IP_DOCKER}:${dataprep_port}/v1/dataprep/ingest" \ + "Data preparation succeeded" \ + "docker-dataprep_upload_file" \ + "" \ + "-F 'files=@${temp_dataprep_file}' -H 'Content-Type: multipart/form-data'" + rm -f "$temp_dataprep_file" # Clean up local file + + validate_docker_sub_service \ + "http://${HOST_IP_DOCKER}:${embedding_port}/v1/embeddings" \ + "embedding" \ + "docker-embedding" \ + '{"input":"What is Deep Learning?"}' + + local test_embedding # Generate or use a fixed embedding + test_embedding=$(python3 -c "import random; embedding = [random.uniform(-1, 1) for _ in range(1024)]; print(embedding)" 2>/dev/null || echo "[0.1, -0.2, ...]") # Fallback + validate_docker_sub_service \ + "http://${HOST_IP_DOCKER}:${retriever_port}/v1/retrieval" \ + "retrieved_docs" \ + "docker-retrieval" \ + "{\"text\":\"What is deep learning?\",\"embedding\":${test_embedding}}" + + validate_docker_sub_service \ + "http://${HOST_IP_DOCKER}:${reranking_port}/v1/reranking" \ + '"score":' \ + "docker-reranking" \ + '{"initial_query":"What is Deep Learning?", "retrieved_docs": [{"text":"Deep Learning is not..."}, {"text":"Deep learning is..."}]}' # Adjusted expected result + + # Docker reference used /v1/chat/completions for vllm + LLM_MODEL_ID=$(curl -s http://${HOST_IP_DOCKER}:${llm_port}/v1/models | jq -r '.data[0].id') + LLM_MODEL_ID=${LLM_MODEL_ID:-"Qwen/Qwen2-7-Instruct"} + validate_docker_sub_service \ + "http://${HOST_IP_DOCKER}:${llm_port}/v1/chat/completions" \ + "content" \ + "docker-llm" \ + '{"model": "'"${LLM_MODEL_ID}"'", "messages": [{"role": "user", "content": "What is Deep Learning?"}], "max_tokens": 17}' + + # Optional: Dataprep Delete test + validate_docker_sub_service \ + "http://${HOST_IP_DOCKER}:${dataprep_port}/v1/dataprep/delete" \ + '"status":true' \ + "docker-dataprep_del" \ + '{"file_path": "all"}' + + else + log ERROR "Invalid DEPLOY_MODE '$DEPLOY_MODE' for sub-service tests." + return 1 + fi + + # --- Report Results --- + if [ ${#failed_services[@]} -eq 0 ]; then + log OK "All sub-service validation checks passed." + else + log ERROR "Sub-service validation finished with failures." + log ERROR "Failed services:" + for service in "${failed_services[@]}"; do + log ERROR "- $service" + done + fi + # Keep the overall script exit code as 1 since the main test failed +} + + +# --- Main test logic --- +access_url="" +CLIENT_POD_NAME="" # For K8s + +if [[ "$DEPLOY_MODE" == "k8s" ]]; then + log INFO "Preparing K8s test environment..." + if ! command_exists kubectl; then + log ERROR "kubectl command not found, cannot run K8s test." + exit 1 + fi + check_k8s_pods_ready # No exit on failure here, just warning + + # Use the generic backend service name, adjust if needed based on DEVICE or other factors + local backend_svc_name="chatqna-backend-server-svc" + + # Reuse or create client pod + CLIENT_POD_NAME=$(kubectl get pod -n "$K8S_NAMESPACE" -l app=client-test -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}' 2>/dev/null | awk '{print $1}') + if [ -z "$CLIENT_POD_NAME" ]; then + log INFO "No running client-test pod found. Creating one..." + # Increased timeout for pod creation/readiness + if ! kubectl run client-test --image=curlimages/curl:latest -n "$K8S_NAMESPACE" --labels="app=client-test" -- sleep infinity; then + log ERROR "Failed to create client-test pod." + exit 1 + fi + log INFO "Waiting for client-test pod to be ready (up to 180s)..." + if ! kubectl wait --for=condition=Ready pod -l app=client-test -n "$K8S_NAMESPACE" --timeout=180s; then + log ERROR "Client-test pod did not become ready in time." + kubectl logs -l app=client-test -n "$K8S_NAMESPACE" --tail=50 >> "$LOG_FILE" + exit 1 + fi + CLIENT_POD_NAME=$(kubectl get pod -n "$K8S_NAMESPACE" -l app=client-test -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}' | awk '{print $1}') + if [ -z "$CLIENT_POD_NAME" ]; then + log ERROR "Failed to get client-test pod name after creation." + exit 1 + fi + log OK "Client-test pod '$CLIENT_POD_NAME' is ready." + else + log INFO "Using existing client-test pod: $CLIENT_POD_NAME" + fi + + mega_service_endpoint=$(get_k8s_service_endpoint "$backend_svc_name" "$K8S_NAMESPACE") + if [ -z "$mega_service_endpoint" ]; then + log ERROR "Failed to determine K8s backend service endpoint for '$backend_svc_name'. Cannot run test." + # Run sub-service tests even if main endpoint discovery fails + run_sub_service_tests + exit 1 + fi + access_url="http://${mega_service_endpoint}/v1/chatqna" + +elif [[ "$DEPLOY_MODE" == "docker" ]]; then + log INFO "Preparing Docker test environment..." + HOST_IP=$(get_host_ip_for_docker) + # Port from compose file for chatqna-xeon-backend-server + BACKEND_PORT="8888" + access_url="http://${HOST_IP}:${BACKEND_PORT}/v1/chatqna" + log INFO "Using host IP: $HOST_IP and port $BACKEND_PORT for Docker test." + +else + log ERROR "Invalid DEPLOY_MODE specified: '$DEPLOY_MODE'. Use 'k8s' or 'docker'." + exit 1 +fi + +JSON_PAYLOAD='{"messages": [{"role": "user", "content": "What is the revenue of Nike in 2023?"}], "max_new_tokens": 100, "stream": false}' + +log INFO "Attempting main connection to ChatQnA backend at: $access_url" +log INFO "Payload: $JSON_PAYLOAD" + +response_code="" +response_body_file=$(mktemp) + +# Perform the main curl command +if [[ "$DEPLOY_MODE" == "k8s" ]]; then + log INFO "Executing main test via K8s pod '$CLIENT_POD_NAME'..." + kubectl_exec_command="curl --max-time 120 -s -w '%{http_code}' -o /tmp/main_resp.body '$access_url' -X POST -d '$JSON_PAYLOAD' -H 'Content-Type: application/json'" + response_code=$(kubectl exec "$CLIENT_POD_NAME" -n "$K8S_NAMESPACE" -- sh -c "$kubectl_exec_command") + # Try to retrieve body if needed, especially on non-200 + kubectl exec "$CLIENT_POD_NAME" -n "$K8S_NAMESPACE" -- cat /tmp/main_resp.body > "$response_body_file" 2>/dev/null + kubectl exec "$CLIENT_POD_NAME" -n "$K8S_NAMESPACE" -- rm -f /tmp/main_resp.body &> /dev/null +else # Docker mode + log INFO "Executing main test directly via host curl..." + response_code=$(http_proxy="" curl --max-time 120 -s -w "%{http_code}" -o "${response_body_file}" "$access_url" -X POST -d "$JSON_PAYLOAD" -H 'Content-Type: application/json') +fi + +log INFO "Main Curl command executed. HTTP Response Code: $response_code" + +if [ -s "$response_body_file" ]; then # Check if file exists and is not empty + log INFO "Response body (first 500 chars):" + head -c 500 "$response_body_file" | tee -a "$LOG_FILE" # Log and print snippet + echo "" # Newline +else + if [[ "$response_code" != "200" ]]; then + log WARN "Response body file is empty or not found. Check connectivity and service logs." + fi +fi +rm -f "$response_body_file" + + +if [[ "$response_code" == "200" ]]; then + log OK "Main ChatQnA connection test successful! Received HTTP 200." + exit 0 +else + log ERROR "Main ChatQnA connection test failed. Received HTTP code: $response_code." + log ERROR "Review logs above. Running detailed sub-service checks..." + # --- Call the sub-service validation function --- + run_sub_service_tests + # --- Exit with failure code --- + log ERROR "Exiting script with failure status." + exit 1 +fi diff --git a/ChatQnA/deployment/update_images.sh b/ChatQnA/deployment/update_images.sh new file mode 100644 index 0000000000..5be6902acc --- /dev/null +++ b/ChatQnA/deployment/update_images.sh @@ -0,0 +1,734 @@ +#!/bin/bash +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + + +set -e +set -o pipefail + +SCRIPT_DIR_UPDATE=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +source "${SCRIPT_DIR_UPDATE}/utils.sh" # Provides log, section_header, command_exists + +repo_path=$(realpath "${SCRIPT_DIR_UPDATE}/../") + +# --- Configuration --- +BUILD_CONTEXT_REL_PATH="deployment/docker_image_build" +BUILD_CONTEXT_PATH="${repo_path}/${BUILD_CONTEXT_REL_PATH}" +COMPOSE_FILE="${BUILD_CONTEXT_PATH}/docker_build_compose.yaml" + +LOG_DIR="${repo_path}/logs" +mkdir -p "$LOG_DIR" +BUILD_LOG_FILE="${LOG_DIR}/docker_compose_build_$(date +%Y%m%d_%H%M%S).log" + +# Registry and Tagging Defaults +REGISTRY_NAME="" # Set via --registry or --setup-registry +TAG="latest" + +# Dependency Repository Defaults +DEFAULT_REPO_TAG_BRANCH="main" +OPEA_REPO_BRANCH="${DEFAULT_REPO_TAG_BRANCH}" +CORE_REPO_BRANCH="${DEFAULT_REPO_TAG_BRANCH}" +#CORE_REPO_URL="https://gitee.com/intel-china/aisolution-core" +CORE_REPO_URL="https://github.com/intel-innersource/frameworks.ai.enterprise-solutions-core" +VLLM_FORK_REPO_URL="https://github.com/HabanaAI/vllm-fork.git" +VLLM_FORK_REPO_TAG="v0.6.6.post1+Gaudi-1.20.0" +VLLM_REPO_URL="https://github.com/vllm-project/vllm.git" +# VLLM version is determined dynamically later by checking out latest tag + +export WORKSPACE=$repo_path # Used by compose files + +declare -A components +components=( + ["chatqna"]="chatqna opea/aisolution/chatqna" + # ["ui-usvc"]="chatqna-ui opea/aisolution/chatqna-ui" # Assuming chatqna-ui maps to a compose service, add to YAML if needed + ["embedding-mosec"]="embedding-mosec opea/aisolution/embedding-mosec" + ["embedding-mosec-endpoint"]="embedding-mosec-endpoint opea/aisolution/embedding-mosec-endpoint" + ["dataprep"]="dataprep opea/aisolution/dataprep" + ["retriever"]="retriever opea/aisolution/retriever" + ["reranking-mosec"]="reranking-mosec opea/aisolution/reranking-mosec" + ["reranking-mosec-endpoint"]="reranking-mosec-endpoint opea/aisolution/reranking-mosec-endpoint" + ["nginx"]="nginx opea/aisolution/nginx" + ["vllm-gaudi"]="vllm-gaudi opea/vllm-gaudi" + ["vllm-cpu"]="vllm opea/vllm" + +) +# DEFAULT_COMPONENTS_LIST will be populated dynamically from COMPOSE_FILE +DEFAULT_COMPONENTS_LIST=() +# yaml_service_names will be populated by populate_default_components_from_compose +# and used in the main script body for hints. +declare -a yaml_service_names + +components_to_build_list=() # User specified components + +# Proxies for Docker build +DOCKER_BUILD_PROXY_ARGS_ARRAY=() +[ -n "$http_proxy" ] && DOCKER_BUILD_PROXY_ARGS_ARRAY+=(--build-arg "http_proxy=$http_proxy") +[ -n "$https_proxy" ] && DOCKER_BUILD_PROXY_ARGS_ARRAY+=(--build-arg "https_proxy=$https_proxy") +[ -n "$no_proxy" ] && DOCKER_BUILD_PROXY_ARGS_ARRAY+=(--build-arg "no_proxy=$no_proxy") + +# --- Functions --- + +usage() { + echo -e "Usage: $0 [OPTIONS] [COMPONENTS_TO_BUILD...]" + echo -e "Builds OPEA microservice images using Docker Compose." + echo -e "Handles Git dependencies efficiently (updates existing repos, clones new ones)." + echo -e "" + echo -e "Options:" + echo -e " --build: Build specified components (or defaults if none specified)." + echo -e " --push: Push built images to the registry." + echo -e " --setup-registry: Setup a local Docker registry (localhost:5000)." + echo -e " --registry : Target Docker registry (e.g., mydockerhub/myproject, docker.io)." + echo -e " --tag : Image tag (default: $TAG)." + echo -e " --core-repo : Git URL for core components (default: $CORE_REPO_URL)." + echo -e " --core-branch : Branch/tag for core components repo (default: $DEFAULT_REPO_TAG_BRANCH)." + echo -e " --opea-branch : Branch/tag for GenAIComps repo (default: $DEFAULT_REPO_TAG_BRANCH)." + echo -e " --no-cache: Add --no-cache flag to docker compose build." + echo -e " --help: Display this help message." + echo -e "" + echo -e "Available components (derived from compose file if found, otherwise from script's map):" + # Dynamically list available components from the 'components' map keys + local available_comp_args=("${!components[@]}") + echo -e " ${available_comp_args[*]}" + echo -e "" + echo -e "Example: Build specific components and push to a custom registry:" + echo -e " $0 --build --push --registry my-registry.com/myorg --tag v1.0 embedding-mosec-usvc" + exit 0 +} + +populate_default_components_from_compose() { + # This function populates the global 'yaml_service_names' array and 'DEFAULT_COMPONENTS_LIST' + yaml_service_names=() # Clear global array for fresh population + local parsed_successfully=false + + if [ ! -f "$COMPOSE_FILE" ]; then + log WARN "Compose file '$COMPOSE_FILE' not found. Cannot dynamically determine default components." + # Fallback logic will be handled after this block + else + log INFO "Deriving default components from $COMPOSE_FILE..." + if command_exists yq; then + log INFO "Attempting to parse $COMPOSE_FILE with yq." + # Try yq (mikefarah/yq v4+) syntax + mapfile -t yaml_service_names < <(yq eval '.services | keys | .[]' "$COMPOSE_FILE" 2>/dev/null) + if [ $? -eq 0 ] && [ ${#yaml_service_names[@]} -gt 0 ]; then + log INFO "Successfully parsed service names using yq (v4+ syntax)." + parsed_successfully=true + else + # Clear if previous attempt failed or returned empty + yaml_service_names=() + log WARN "yq (v4+ syntax) failed or returned no services. Trying alternative yq syntax (Python yq or older mikefarah/yq)." + # Try Python yq syntax (kislyuk/yq) + mapfile -t yaml_service_names < <(yq -r '.services | keys | .[]' "$COMPOSE_FILE" 2>/dev/null) + if [ $? -eq 0 ] && [ ${#yaml_service_names[@]} -gt 0 ]; then + log INFO "Successfully parsed service names using yq (Python yq or older mikefarah/yq syntax)." + parsed_successfully=true + else + yaml_service_names=() # Clear again + log WARN "Alternative yq syntax also failed or returned no services. Will try sed fallback." + fi + fi + fi + + # Fallback to sed if yq is not available or all yq attempts failed + if ! $parsed_successfully; then + if ! command_exists yq; then + log INFO "yq command not found. Using sed to parse $COMPOSE_FILE (less robust)." + else + log INFO "All yq parsing attempts failed. Using sed to parse $COMPOSE_FILE (less robust)." + fi + # This sed command extracts service names indented by 2 spaces under a 'services:' block. + mapfile -t yaml_service_names < <(sed -n '/^services:/,/^[^ ]/ { /^[ ]*$/d; /^services:/d; /^[ ]{2}\S[^:]*:/ { s/^[ ]{2}\([^:]*\):.*/\1/; p } }' "$COMPOSE_FILE") + if [ ${#yaml_service_names[@]} -gt 0 ]; then + log INFO "Successfully parsed service names using sed." + parsed_successfully=true + fi + fi + fi # End of if [ -f "$COMPOSE_FILE" ] + + if ! $parsed_successfully || [ ${#yaml_service_names[@]} -eq 0 ]; then + if [ -f "$COMPOSE_FILE" ]; then # Only log this specific warning if the file existed but parsing failed + log WARN "Failed to parse services from '$COMPOSE_FILE' using all methods, or the file has no services defined under a 'services:' key." + fi + log WARN "Falling back to all known components in the script's 'components' map as potential defaults." + DEFAULT_COMPONENTS_LIST=("${!components[@]}") + if [ ${#DEFAULT_COMPONENTS_LIST[@]} -eq 0 ]; then + log ERROR "The 'components' map in the script is also empty. Cannot proceed." + exit 1 # Exiting here because no components can be determined. + fi + yaml_service_names=() # Ensure it's empty if we are using fallback for DEFAULT_COMPONENTS_LIST + return + fi + + # Populate DEFAULT_COMPONENTS_LIST based on successfully parsed yaml_service_names + local temp_default_list=() + for service_name_from_yaml in "${yaml_service_names[@]}"; do + local found_arg_name="" + for comp_arg_key in "${!components[@]}"; do + local component_details="${components[$comp_arg_key]}" + local compose_service_name_in_map="${component_details%% *}" # Get first word + + if [[ "$compose_service_name_in_map" == "$service_name_from_yaml" ]]; then + found_arg_name="$comp_arg_key" + break + fi + done + + if [[ -n "$found_arg_name" ]]; then + temp_default_list+=("$found_arg_name") + else + log INFO "Service '$service_name_from_yaml' from $COMPOSE_FILE has no mapping in 'components' array. It won't be a default arg." + fi + done + DEFAULT_COMPONENTS_LIST=("${temp_default_list[@]}") + log INFO "Default components derived from compose file: ${DEFAULT_COMPONENTS_LIST[*]}" + if [ ${#DEFAULT_COMPONENTS_LIST[@]} -eq 0 ] && [ ${#yaml_service_names[@]} -gt 0 ]; then # If YAML had services but none mapped + log WARN "Services were found in $COMPOSE_FILE (${yaml_service_names[*]}), but none could be mapped to known component arguments for the default list." + elif [ ${#DEFAULT_COMPONENTS_LIST[@]} -eq 0 ]; then # If YAML had no services or they didn't map + log WARN "No services from $COMPOSE_FILE could be mapped to known component arguments. Default list is empty." + fi +} + + +setup_local_registry_func() { + local local_reg_name_const="local-docker-registry" + local local_reg_port_const=5000 + local local_reg_image_const="registry:2" + + if $do_setup_registry_flag && [ -z "$REGISTRY_NAME" ]; then + REGISTRY_NAME="localhost:${local_reg_port_const}" + log INFO "Using local registry: $REGISTRY_NAME" + fi + + if ! $do_setup_registry_flag; then return 0; fi + + log INFO "Setting up local registry '$local_reg_name_const'..." + + if docker ps --format '{{.Names}}' | grep -qx "^${local_reg_name_const}$" > /dev/null; then + log INFO "Local registry '$local_reg_name_const' already running." + return 0 + fi + + if docker ps -aq -f name="^${local_reg_name_const}$" > /dev/null; then + log INFO "Starting existing stopped local registry '$local_reg_name_const'..." + if docker start "$local_reg_name_const"; then + log OK "Started existing local registry '$local_reg_name_const'." + return 0 + else + log WARN "Failed to start existing registry container. Removing and recreating." + docker rm -f "$local_reg_name_const" &>/dev/null || log WARN "Failed to remove existing container '$local_reg_name_const'." + fi + fi + + log INFO "Creating new local registry container '$local_reg_name_const' on port $local_reg_port_const..." + if docker run -d -p "${local_reg_port_const}:${local_reg_port_const}" --restart always --name "$local_reg_name_const" "$local_reg_image_const"; then + log OK "Local registry '$local_reg_name_const' started successfully." + return 0 + else + log ERROR "Failed to start new local registry container '$local_reg_name_const'." + if [[ "$REGISTRY_NAME" == "localhost:${local_reg_port_const}" ]]; then + log WARN "Pushing to $REGISTRY_NAME might fail." + fi + return 1 + fi +} + + +clone_or_update_repo() { + local repo_url="$1" + local target_path="$2" + local branch_or_tag="$3" + local git_clone_base_flags="--config advice.detachedHead=false" + + log INFO "Syncing repository $repo_url ($branch_or_tag) in $target_path" + + if should_clone_repo "$repo_url" "$target_path"; then + safe_clone_repo "$repo_url" "$target_path" "$branch_or_tag" "$git_clone_base_flags" + else + update_existing_repo "$repo_url" "$target_path" "$branch_or_tag" || { + log WARN "Update failed. Re-cloning..." + safe_clone_repo "$repo_url" "$target_path" "$branch_or_tag" "$git_clone_base_flags" + } + fi +} + +should_clone_repo() { + local repo_url="$1" + local target_path="$2" + + if [ ! -d "$target_path" ] || [ ! -d "$target_path/.git" ]; then + return 0 + fi + + local current_url + if ! current_url=$(git -C "$target_path" config --get remote.origin.url); then + return 0 + fi + + if [[ "$current_url" != "$repo_url" ]]; then + log WARN "Remote URL mismatch. Forcing re-clone." + return 0 + fi + + return 1 +} + +update_existing_repo() { + local repo_url="$1" + local target_path="$2" + local branch_or_tag="$3" + + git -C "$target_path" reset --hard HEAD || return 1 + git -C "$target_path" clean -fd || return 1 + git -C "$target_path" fetch --all --prune --tags || return 1 + + if [[ "$repo_url" == "$VLLM_REPO_URL" ]]; then + checkout_vllm_tag "$target_path" + else + checkout_normal_repo "$target_path" "$branch_or_tag" + fi +} + +checkout_vllm_tag() { + local target_path="$1" + + git -C "$target_path" fetch --tags > /dev/null 2>&1 + # local latest_tag=$(git -C "$target_path" describe --tags "$(git -C "$target_path" rev-list --tags --max-count=1)") + local latest_tag="v0.8.5" + if [ -z "$latest_tag" ]; then + log ERROR "No vLLM tags found!" + return 1 + fi + + log INFO "Checking out vLLM tag: $latest_tag" + git -C "$target_path" checkout "$latest_tag" --force +} + +checkout_normal_repo() { + local target_path="$1" + local branch_or_tag="$2" + + git -C "$target_path" checkout "$branch_or_tag" --force || return 1 + + if ! is_git_tag "$target_path" "$branch_or_tag"; then + log INFO "Pulling branch updates..." + git -C "$target_path" pull --rebase || git -C "$target_path" pull + fi +} + +safe_clone_repo() { + local repo_url="$1" + local target_path="$2" + local branch_or_tag="$3" + local base_flags="$4" + + rm -rf "$target_path" + + if [[ "$repo_url" == "$VLLM_REPO_URL" ]]; then + git clone $base_flags --no-single-branch "$repo_url" "$target_path" + checkout_vllm_tag "$target_path" + else + git clone $base_flags --depth 1 --branch "$branch_or_tag" "$repo_url" "$target_path" + fi +} + +is_git_tag() { + local target_path="$1" + local ref="$2" + git -C "$target_path" rev-parse --verify --quiet "refs/tags/$ref" >/dev/null +} + + +tag_and_push_func() { + if [[ "$do_push_flag" == "false" ]]; then return 0; fi + + local target_registry_url="$1" + # base_image_name is now the name segment that appears AFTER the registry in the final remote name + # e.g., "chatqna" or "opea/aisolution/chatqna" + local base_image_name="$2" + local image_tag_to_push="$3" + + if [ -z "$target_registry_url" ] && [[ "$base_image_name" != */* ]]; then + # If no registry specified AND base_image_name is simple (e.g. "chatqna", not "myuser/chatqna") + # then we assume it's for official Docker Hub image, which is not what we usually do here. + # Or, if user wants to push to their Docker Hub, they should specify --registry + log WARN "No registry URL specified (--registry) and base image name '$base_image_name' is simple." + log WARN "If pushing to Docker Hub, please use --registry ." + log WARN "Skipping push for ${base_image_name}:${image_tag_to_push}." + return 1 + fi + + # This is the name of the image that should exist locally, prepared by the aliasing step in build_images_with_compose + # e.g., "chatqna:latest" or "opea/aisolution/chatqna:latest" + local local_full_image_name="${base_image_name}:${image_tag_to_push}" + + # Construct the remote image name + local remote_full_image_name + if [ -n "$target_registry_url" ]; then + remote_full_image_name="${target_registry_url}/${base_image_name}:${image_tag_to_push}" + else + # No target_registry_url, means base_image_name should be the full name (e.g. user/repo) + remote_full_image_name="${base_image_name}:${image_tag_to_push}" + fi + + + if ! docker image inspect "${local_full_image_name}" > /dev/null 2>&1; then + log WARN "Local image ${local_full_image_name} not found. Cannot tag for remote or push. Build/Aliasing might have failed." + return 1 + fi + + log INFO "Tagging $local_full_image_name -> $remote_full_image_name" + if ! docker tag "${local_full_image_name}" "${remote_full_image_name}"; then + log ERROR "Failed to tag ${local_full_image_name} for remote push to ${remote_full_image_name}." + return 1 + fi + + log INFO "Pushing ${remote_full_image_name}..." + if ! docker push "${remote_full_image_name}"; then + log ERROR "Failed to push ${remote_full_image_name}." + docker rmi "${remote_full_image_name}" > /dev/null 2>&1 || log WARN "Could not remove local tag ${remote_full_image_name} after failed push." + return 1 + else + log OK "Successfully pushed ${remote_full_image_name}." + # Optional: docker rmi "${remote_full_image_name}" > /dev/null 2>&1 # Clean up the remote-named local tag + fi + return 0 +} + +build_images_with_compose() { + local -a services_to_build_array # Use local array for services + services_to_build_array=("$@") # Capture all arguments into the array + + if [[ "$do_build_flag" == "false" ]]; then + log INFO "Skipping image build (--build not specified)." + return 0 + fi + if [ ${#services_to_build_array[@]} -eq 0 ]; then + log WARN "No valid services specified for build. Skipping." + return 0 + fi + + # --- 1. Sync Dependencies --- + section_header "Syncing Dependencies into Build Context" + log INFO "Build context path: ${BUILD_CONTEXT_PATH}" + mkdir -p "${BUILD_CONTEXT_PATH}" + if ! command_exists git; then log ERROR "'git' command not found."; return 1; fi + + # Chain dependency syncing. If one fails, subsequent ones are skipped. + ( \ + clone_or_update_repo "https://github.com/opea-project/GenAIComps.git" "${BUILD_CONTEXT_PATH}/GenAIComps" "$OPEA_REPO_BRANCH" && \ + clone_or_update_repo "$CORE_REPO_URL" "${BUILD_CONTEXT_PATH}/aisolution-core" "$CORE_REPO_BRANCH" && \ + clone_or_update_repo "$VLLM_REPO_URL" "${BUILD_CONTEXT_PATH}/vllm" "main" && \ + clone_or_update_repo "$VLLM_FORK_REPO_URL" "${BUILD_CONTEXT_PATH}/vllm-fork" "$VLLM_FORK_REPO_TAG" \ + ) || { log ERROR "Dependency syncing failed. Aborting build."; return 1; } + log OK "Dependencies synced successfully into build context." + + # --- 2. Modify Dockerfiles (Optional - consider build args instead) --- + section_header "Checking Dockerfile Modifications (if needed)" + if [[ "${OPEA_REPO_BRANCH}" != "main" ]]; then + log INFO "Modifying Dockerfiles for GenAIComps branch: ${OPEA_REPO_BRANCH}" + local OLD_STRING_GENAI="RUN git clone --depth 1 https://github.com/opea-project/GenAIComps.git" + local NEW_STRING_GENAI="RUN git clone --depth 1 --branch ${OPEA_REPO_BRANCH} https://github.com/opea-project/GenAIComps.git" + find "${BUILD_CONTEXT_PATH}" -type f \( -name "Dockerfile" -o -name "Dockerfile.*" \) -print0 | while IFS= read -r -d $'\0' file; do + if grep -qF "$OLD_STRING_GENAI" "$file"; then + log INFO " Updating GenAIComps clone in: $file" + sed -i.bak "s|$OLD_STRING_GENAI|$NEW_STRING_GENAI|g" "$file" && rm "${file}.bak" + fi + done + fi + #local CORE_REPO_DEFAULT_MAIN_URL="https://gitee.com/intel-china/aisolution-core" # Default assumed in Dockerfiles + local CORE_REPO_DEFAULT_MAIN_URL="https://github.com/intel-innersource/frameworks.ai.enterprise-solutions-core" + if [[ "${CORE_REPO_BRANCH}" != "main" ]] || [[ "${CORE_REPO_URL}" != "${CORE_REPO_DEFAULT_MAIN_URL}" ]]; then + log INFO "Modifying Dockerfiles for Core repo URL: ${CORE_REPO_URL}, branch: ${CORE_REPO_BRANCH}" + local OLD_STRING_CORE="RUN git clone ${CORE_REPO_DEFAULT_MAIN_URL}" # Might be fragile if Dockerfile changes + local NEW_STRING_CORE="RUN git clone --depth 1 --branch ${CORE_REPO_BRANCH} ${CORE_REPO_URL}" + find "${BUILD_CONTEXT_PATH}" -type f \( -name "Dockerfile" -o -name "Dockerfile.*" \) -print0 | while IFS= read -r -d $'\0' file; do + if grep -qF "$OLD_STRING_CORE" "$file"; then + log INFO " Updating Core repo clone in: $file" + sed -i.bak "s|$OLD_STRING_CORE|$NEW_STRING_CORE|g" "$file" && rm "${file}.bak" + fi + done + fi + log OK "Dockerfile modification checks complete." + + # --- 3. Docker Compose Build --- + section_header "Building Images with Docker Compose" + log INFO "Using Compose file: ${COMPOSE_FILE}" + log INFO "Building services: ${services_to_build_array[*]}" + log INFO "Log file: ${BUILD_LOG_FILE}" + + if [ ! -f "$COMPOSE_FILE" ]; then log ERROR "Compose file not found: $COMPOSE_FILE"; return 1; fi + + local -a cmd_array + cmd_array=(docker compose -f "${COMPOSE_FILE}" build) + # Add proxy build args if any + if [ ${#DOCKER_BUILD_PROXY_ARGS_ARRAY[@]} -gt 0 ]; then + cmd_array+=("${DOCKER_BUILD_PROXY_ARGS_ARRAY[@]}") + fi + if $do_no_cache_flag; then cmd_array+=(--no-cache); fi + cmd_array+=("${services_to_build_array[@]}") # Add services to build + + log INFO "Executing build command:" + # Log the command carefully, handling potential spaces in args by quoting for display + local display_cmd="" + for arg in "${cmd_array[@]}"; do display_cmd+="'$arg' "; done + log INFO "$display_cmd" + + # Export variables for compose file substitution, if they are used like ${VAR} in compose + export REGISTRY="${REGISTRY_NAME}" # For image: ${REGISTRY:-opea/aisolution}/... + export IMAGE_TAG="${TAG}" # For image: ...:${IMAGE_TAG:-latest} + + # Execute the command + # Tee output to log file and also capture exit status + local build_status=0 + { "${cmd_array[@]}" 2>&1 || build_status=$?; } | tee -a "${BUILD_LOG_FILE}" + # Check the captured exit status + if [ $build_status -ne 0 ]; then + log ERROR "Docker Compose build failed with status $build_status. See log: ${BUILD_LOG_FILE}" + return 1 + else + log OK "Docker Compose build completed successfully." + fi + + + # --- 4. Tag Built Images (to prepare for push logic) --- + # This step ensures that a local image exists with the name that tag_and_push_func will expect. + section_header "Applying Local Aliases for Push Consistency" + local tag_success_count=0 + local tag_fail_count=0 + + for component_arg_name in "${components_to_build_list[@]}"; do + local service_name_from_map base_image_name_from_map + read -r service_name_from_map base_image_name_from_map <<< "${components[$component_arg_name]}" + + local service_was_built=false + for srv_in_build in "${services_to_build_array[@]}"; do + if [[ "$srv_in_build" == "$service_name_from_map" ]]; then + service_was_built=true + break + fi + done + + if ! $service_was_built; then + continue + fi + + # Determine the image name as built by Docker Compose + local built_image_by_compose + if [ -n "$REGISTRY" ]; then # Use exported REGISTRY var which was used for 'docker compose build' + built_image_by_compose="${REGISTRY}/${service_name_from_map}:${TAG}" + else + built_image_by_compose="opea/aisolution/${service_name_from_map}:${TAG}" + fi + + # Determine the target local alias name that tag_and_push_func will expect. + # This depends on how tag_and_push_func will be called (which depends on global REGISTRY_NAME for push). + local target_local_alias_base_name_for_push + if [ -n "$REGISTRY_NAME" ]; then # REGISTRY_NAME here is the global one for the upcoming push operation + target_local_alias_base_name_for_push="$service_name_from_map" # e.g., "chatqna" + else + target_local_alias_base_name_for_push="$base_image_name_from_map" # e.g., "opea/aisolution/chatqna" + fi + local final_local_alias_for_push="${target_local_alias_base_name_for_push}:${TAG}" + + + if docker image inspect "$built_image_by_compose" > /dev/null 2>&1; then + if [[ "$built_image_by_compose" != "$final_local_alias_for_push" ]]; then + log INFO "Aliasing ${built_image_by_compose} -> ${final_local_alias_for_push} (for push consistency)" + if docker tag "$built_image_by_compose" "$final_local_alias_for_push"; then + ((tag_success_count++)) + else + log WARN "Failed to alias ${built_image_by_compose} to ${final_local_alias_for_push}" + ((tag_fail_count++)) + fi + else + log INFO "Image ${final_local_alias_for_push} already exists (as built by compose or matches target alias name)." + ((tag_success_count++)) + fi + else + log WARN "Could not find image '${built_image_by_compose}' supposedly built by Docker Compose for service '${service_name_from_map}'. Skipping local aliasing." + ((tag_fail_count++)) + fi + done + + log OK "Local aliasing for push consistency complete. Success: $tag_success_count, Failed/Skipped: $tag_fail_count." + if [ $tag_fail_count -gt 0 ]; then + log WARN "Some images may not have the expected local alias for push. Subsequent push might fail for these." + # return 1; # Decide if this should be a fatal error + fi + + return 0 +} + +# --- Argument Parsing --- +do_build_flag=false +do_push_flag=false +do_setup_registry_flag=false +do_no_cache_flag=false + +while [ $# -gt 0 ]; do + case "$1" in + --build) do_build_flag=true ;; + --push) do_push_flag=true ;; + --setup-registry) do_setup_registry_flag=true ;; + --registry) REGISTRY_NAME="$2"; shift ;; + --tag) TAG="$2"; shift ;; + --core-repo) CORE_REPO_URL="$2"; shift ;; + --core-branch) CORE_REPO_BRANCH="$2"; shift ;; + --opea-branch) OPEA_REPO_BRANCH="$2"; shift ;; + --no-cache) do_no_cache_flag=true ;; + --help) usage ;; + -*) log ERROR "Unknown option: $1"; usage ;; + *) components_to_build_list+=("$1") ;; # Collects component argument names + esac + shift +done + +if $do_setup_registry_flag && [ -n "$REGISTRY_NAME" ]; then + log WARN "Both --setup-registry and --registry specified, using --registry value: $REGISTRY_NAME" +fi + +# --- Main Logic --- +section_header "Image Update Script Started" +log INFO "Repo Path: $repo_path, Build Context: $BUILD_CONTEXT_PATH" +log INFO "Config - Build: $do_build_flag, Push: $do_push_flag, Setup Registry: $do_setup_registry_flag, Tag: $TAG" +log INFO "Config - Registry: ${REGISTRY_NAME:-'Not set (will use compose default or opea/aisolution prefix)'}, No Cache: $do_no_cache_flag" +log INFO "Config - Core Repo: $CORE_REPO_URL ($CORE_REPO_BRANCH)" +log INFO "Config - OPEA Repo: ($OPEA_REPO_BRANCH)" +log INFO "Config - vLLM Fork: $VLLM_FORK_REPO_URL ($VLLM_FORK_REPO_TAG)" + +# Populate DEFAULT_COMPONENTS_LIST and global yaml_service_names from docker_build_compose.yaml +populate_default_components_from_compose + + +if ! setup_local_registry_func; then + if $do_push_flag && $do_setup_registry_flag && [[ "$REGISTRY_NAME" == "localhost:"* ]] ; then + log ERROR "Local registry setup failed, and it was requested for push. Aborting." + exit 1 + else + log WARN "Failed to set up local registry. Continuing..." + fi +fi + +# Determine components to process based on user input or defaults +if [ ${#components_to_build_list[@]} -eq 0 ]; then + if [ ${#DEFAULT_COMPONENTS_LIST[@]} -eq 0 ]; then + log ERROR "No specific components listed for build, and could not determine default components (e.g., from $COMPOSE_FILE or internal map was empty)." + log ERROR "Please specify components to build or ensure defaults can be determined." + usage # Show usage, which lists available components from the 'components' map + exit 1 + fi + log INFO "No specific components listed, processing default components derived from $COMPOSE_FILE (or fallback)." + components_to_build_list=("${DEFAULT_COMPONENTS_LIST[@]}") +else + log INFO "Processing specified components: ${components_to_build_list[*]}" +fi + +# Map component arguments to compose service names +services_to_process_for_compose=() # Docker compose service names +valid_component_args_for_push=() # Valid component argument names for the push loop +for comp_arg in "${components_to_build_list[@]}"; do + if [[ -n "${components[$comp_arg]}" ]]; then + read -r service_name _ <<< "${components[$comp_arg]}" + services_to_process_for_compose+=("$service_name") + valid_component_args_for_push+=("$comp_arg") + else + log WARN "Unknown component argument '$comp_arg'. Skipping." + # Check if it's a direct service name from compose file but not in our map + # These variables are reset/re-assigned in each iteration of the loop + is_direct_service=false + service_names_to_check_hint=() # Re-initialize array + + # Check if the global yaml_service_names array has elements. + # This array is populated by populate_default_components_from_compose. + if [ ${#yaml_service_names[@]} -gt 0 ]; then + service_names_to_check_hint=("${yaml_service_names[@]}") + else + # Fallback: check against known service names from the components map itself + for comp_key_for_hint in "${!components[@]}"; do + service_names_to_check_hint+=("$(echo "${components[$comp_key_for_hint]}" | awk '{print $1}')") + done + fi + + for yaml_service_name_check in "${service_names_to_check_hint[@]}"; do + if [[ "$comp_arg" == "$yaml_service_name_check" ]]; then + is_direct_service=true; break + fi + done + if $is_direct_service; then + log WARN "Hint: '$comp_arg' looks like a service name from $COMPOSE_FILE. To build it, ensure it has an entry in the 'components' map in this script." + fi + fi +done + +if [ ${#services_to_process_for_compose[@]} -eq 0 ]; then + log WARN "No valid components selected for processing. Exiting." + exit 0 +fi +log INFO "Mapped to Docker Compose services for build: ${services_to_process_for_compose[*]}" + + +# Build images if requested +if $do_build_flag; then + # Pass the array of compose service names to the build function + if ! build_images_with_compose "${services_to_process_for_compose[@]}"; then + log ERROR "Image building process failed." + exit 1 + fi +else + log INFO "Skipping build phase (--build not specified)." +fi + +# Push images if requested +if $do_push_flag; then + section_header "Pushing Images" + # REGISTRY_NAME is the global target registry for push. + # tag_and_push_func has a check for empty REGISTRY_NAME if base_image_name is simple. + # For consistency, we could add a primary check here too. + if [ -z "$REGISTRY_NAME" ]; then + # Check if any image to be pushed implies a namespaced image name (e.g. myuser/myimage) + # This is a bit more complex to check perfectly here without inspecting all base_names. + # The check within tag_and_push_func is more robust per image. + log WARN "Target registry not specified via --registry. Push might be ambiguous or fail for non-namespaced images." + log WARN "If pushing to Docker Hub, provide --registry ." + fi + log INFO "Attempting to push to registry: '${REGISTRY_NAME:-"Default (e.g., Docker Hub, image name must be namespaced)"}' with tag: $TAG" + + push_failed_count=0 + # Iterate using valid_component_args_for_push which are the user-facing names that were validated + for component_arg_name in "${valid_component_args_for_push[@]}"; do + # service_name_from_map: e.g., "chatqna" + # base_image_name_from_components_map: e.g., "opea/aisolution/chatqna" + read -r service_name_from_map base_image_name_from_components_map <<< "${components[$component_arg_name]}" + + # Determine the base name to use for the tag_and_push_func call. + # This is the name segment that appears AFTER the registry in the final remote name. + # This variable is reset/re-assigned in each iteration of the loop + base_name_for_push_call="" + if [ -n "$REGISTRY_NAME" ]; then + # If a registry is specified for push, the path on that registry is just the service name. + base_name_for_push_call="$service_name_from_map" # e.g., "chatqna" + else + # If no registry specified (e.g. pushing "opea/aisolution/chatqna" to Docker Hub under implicit user) + # the base_image_name itself is the full path. + base_name_for_push_call="$base_image_name_from_components_map" # e.g., "opea/aisolution/chatqna" + fi + + log INFO "--- Pushing component: $component_arg_name (image base for push: ${base_name_for_push_call}:${TAG}) ---" + if ! tag_and_push_func "$REGISTRY_NAME" "$base_name_for_push_call" "$TAG"; then + log WARN "Failed or skipped push for component: $component_arg_name" + ((push_failed_count++)) + fi + done + + if [ $push_failed_count -gt 0 ]; then + log ERROR "$push_failed_count image(s) failed to push." + # exit 1 # Optional: exit if any push fails + else + log OK "All requested images pushed successfully." + fi +else + log INFO "Skipping push phase (--push not specified)." +fi + + +section_header "Image Update Script Finished Successfully" +exit 0 diff --git a/ChatQnA/deployment/utils.sh b/ChatQnA/deployment/utils.sh new file mode 100644 index 0000000000..41718e7022 --- /dev/null +++ b/ChatQnA/deployment/utils.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + + +# ========= Shared logging utilities =========== +LOG_FILE=${LOG_FILE:-deploy.log} + +# ================ ANSI COLORS ================= +COLOR_RESET="\033[0m" +COLOR_OK="\033[1;32m" +COLOR_ERROR="\033[1;31m" + +# =============== LOGGING FUNCTIONS ============ +log() { + local level=$1 + local message=$2 + local icon color="" + case "$level" in + INFO) icon="📘"; color="";; + OK) icon="✅"; color=$COLOR_OK;; + ERROR) icon="❌"; color=$COLOR_ERROR;; + *) icon="🔹"; color="";; + esac + + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + local plain="$timestamp $icon [$level] $message" + + printf "%s %b\n" "$timestamp" "${color}${icon} [$level] $message${COLOR_RESET}" + echo "$plain" >> "$LOG_FILE" +} + +section_header() { + local title="$1" + local width=56 + local border_top="┌" + local border_side="│" + local border_bot="└" + local fill_char="=" + + local title_len=${#title} + local pad=$(( (width - title_len) / 2 )) + local pad_left=$(printf '%*s' "$pad" "") + local pad_right=$(printf '%*s' "$((width - pad - title_len))" "") + + echo "" | tee -a "$LOG_FILE" + echo "${border_top}$(printf '%*s' $width '' | tr ' ' "$fill_char")┐" | tee -a "$LOG_FILE" + printf "${border_side}%-${width}s${border_side}\n" "${pad_left}${title}${pad_right}" | tee -a "$LOG_FILE" + echo "${border_bot}$(printf '%*s' $width '' | tr ' ' "$fill_char")┘" | tee -a "$LOG_FILE" +} + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + + +progress_bar() { + local duration=${1:-60} + local max_cols=$(($(tput cols)-10)) + local cols=0 + + for ((i=0; i<=$duration; i++)); do + percent=$((100*i/duration)) + + cols=$((max_cols*i/duration)) + + printf "\r[" + for ((j=0; j<$cols; j++)); do printf "#"; done + for ((j=$cols; j<$max_cols; j++)); do printf " "; done + printf "] %3d%%" $percent + + sleep 1 + done + printf "\n" +} + +get_huggingface_token() { + local token_file="$HOME/.cache/huggingface/token" + if [ -f "$token_file" ] && [ -r "$token_file" ]; then + cat "$token_file" | tr -d '\n' + return 0 + else + return 1 + fi +} + From 40eeab8cd5ea02c57fa5cc444f18ad27fef70db6 Mon Sep 17 00:00:00 2001 From: "Yao, Qing" Date: Fri, 23 May 2025 14:01:06 +0800 Subject: [PATCH 2/3] Fix image names and docker compose yaml path Signed-off-by: Yao, Qing --- ChatQnA/deployment/install_chatqna.sh | 15 +- ChatQnA/deployment/one_click_chatqna.sh | 6 +- ChatQnA/deployment/set_values.sh | 175 ++++------ ChatQnA/deployment/update_images.sh | 414 +++++++----------------- 4 files changed, 191 insertions(+), 419 deletions(-) diff --git a/ChatQnA/deployment/install_chatqna.sh b/ChatQnA/deployment/install_chatqna.sh index 7c60593584..bc57d5289a 100644 --- a/ChatQnA/deployment/install_chatqna.sh +++ b/ChatQnA/deployment/install_chatqna.sh @@ -8,9 +8,9 @@ source "${SCRIPT_DIR_INSTALL}/utils.sh" # Assuming utils.sh is in the same direc # LOG_FILE="install_chatqna.log" # Optional: specific log file # Paths -repo_path=$(realpath "$SCRIPT_DIR_INSTALL/../") -manifests_path="$repo_path/deployment/kubernetes" -compose_path="$repo_path/deployment/docker_compose" +chatqna_dir=$(realpath "$SCRIPT_DIR_INSTALL/../") +manifests_path="$chatqna_dir/kubernetes/gmc" +compose_path="$chatqna_dir/docker_compose" set_values_script_path="$SCRIPT_DIR_INSTALL/set_values.sh" # Namespaces @@ -46,7 +46,7 @@ get_available_devices() { # List subdirectories that presumably correspond to devices (cd "$search_path" && find . -maxdepth 1 -mindepth 1 -type d -exec basename {} \; | paste -sd ',' || echo "gaudi,xeon") } -available_devices=$(get_available_devices) # Call once or dynamically if DEPLOY_MODE changes before usage +available_devices="gaudi,xeon" # Call once or dynamically if DEPLOY_MODE changes before usage function usage() { # Update available_devices if DEPLOY_MODE might change before this is called @@ -267,8 +267,11 @@ function clear_k8s_deployment() { function start_docker_deployment() { local target_device="$1" - local target_compose_dir="$compose_path/$target_device" - + if [ "$target_device" = "gaudi" ]; then + local target_compose_dir="${chatqna_dir}/docker_compose/intel/hpu/${DEVICE}" + elif [ "$target_device" = "xeon" ]; then + local target_compose_dir="${chatqna_dir}/docker_compose/intel/cpu/${DEVICE}" + fi section_header "Starting Docker Compose deployment for DEVICE: $target_device" if [ ! -d "$target_compose_dir" ]; then diff --git a/ChatQnA/deployment/one_click_chatqna.sh b/ChatQnA/deployment/one_click_chatqna.sh index 9671926789..56022578d3 100644 --- a/ChatQnA/deployment/one_click_chatqna.sh +++ b/ChatQnA/deployment/one_click_chatqna.sh @@ -109,9 +109,9 @@ echo # Model Configuration Parameters (Optional) log INFO "--- Model Configuration (optional, press Enter to use defaults from sub-scripts) ---" -read -p "Enter Embedding Model ID (e.g., BAAI/bge-m3): " EMBED_MODEL -read -p "Enter Reranking Model ID (e.g., BAAI/bge-reranker-v2-m3): " RERANK_MODEL_ID -read -p "Enter LLM Model ID (e.g., Qwen/Qwen2-7B-Instruct): " LLM_MODEL_ID +read -p "Enter Embedding Model ID (e.g., BAAI/bge-base-en-v1.5): " EMBED_MODEL +read -p "Enter Reranking Model ID (e.g., BAAI/bge-reranker-base): " RERANK_MODEL_ID +read -p "Enter LLM Model ID (e.g., meta-llama/Meta-Llama-3-8B-Instruct): " LLM_MODEL_ID read -p "Enter Data Mount Directory [default: ./data]: " MOUNT_DIR echo diff --git a/ChatQnA/deployment/set_values.sh b/ChatQnA/deployment/set_values.sh index c564a51581..0627c7b23d 100644 --- a/ChatQnA/deployment/set_values.sh +++ b/ChatQnA/deployment/set_values.sh @@ -6,23 +6,24 @@ SCRIPT_DIR_SETVALUES=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd) source "${SCRIPT_DIR_SETVALUES}/utils.sh" # Assuming utils.sh is in the same directory # LOG_FILE="set_values.log" # Optional: specific log file -REPO_DIR=$(realpath "$SCRIPT_DIR_SETVALUES/../") +CHATQNA_DIR=$(realpath "$SCRIPT_DIR_SETVALUES/../") # Default values DEVICE="xeon" # Mandatory, e.g., xeon, gaudi DEPLOY_MODE="docker" # Default deployment mode REGISTRY="" # Renamed from REPOSITORY for consistency with other scripts' params TAG="latest" -EMBED_MODEL="" -RERANK_MODEL_ID="" -LLM_MODEL_ID="" +EMBED_MODEL="" # User input for embedding model +RERANK_MODEL_ID="" # User input for rerank model +LLM_MODEL_ID="" # User input for LLM model HUGGINGFACEHUB_API_TOKEN="" HTTP_PROXY="" HTTPS_PROXY="" -NO_PROXY_BASE="cluster.local,localhost,127.0.0.1,chatqna-xeon-ui-server,chatqna-xeon-backend-server,dataprep-service,mosec-embedding-service,embedding,retriever,reranking,mosec-reranking-service,vllm-service" # Added localhost, 127.0.0.1 +# Updated NO_PROXY_BASE to include static entries from the new set_env.sh +# The dynamic $JAEGER_IP part from new set_env.sh will be appended by that script itself +NO_PROXY_BASE="cluster.local,localhost,127.0.0.1,chatqna-xeon-ui-server,chatqna-xeon-backend-server,dataprep-redis-service,tei-embedding-service,retriever,tei-reranking-service,tgi-service,vllm-service,jaeger,prometheus,grafana,node-exporter" NO_PROXY="$NO_PROXY_BASE" MOUNT_DIR="" -# Removed ip_address from here, will be determined in update_docker_config if needed or use localhost/service_name # Function to display usage information usage() { @@ -32,9 +33,9 @@ usage() { echo " -r Container registry/prefix (e.g., myregistry.com/myorg)." echo " -t Image tag (default: latest)." echo " -u Data mount directory for Docker volumes (default: ./data)." - echo " -e Embedding model name/path." - echo " -a Reranking model ID." - echo " -l LLM model ID." + echo " -e Embedding model ID (e.g., BAAI/bge-base-en-v1.5)." + echo " -a Reranking model ID (e.g., BAAI/bge-reranker-base)." + echo " -l LLM model ID (e.g., meta-llama/Meta-Llama-3-8B-Instruct)." echo " -g HuggingFaceHub API token." echo " -p HTTP proxy URL." echo " -s HTTPS proxy URL." @@ -53,7 +54,7 @@ while getopts "d:m:r:t:e:a:l:g:p:s:n:u:h" opt; do case $opt in d) DEVICE="$OPTARG" ;; m) DEPLOY_MODE="$OPTARG" ;; - r) REGISTRY="$OPTARG" ;; # Changed from REPOSITORY + r) REGISTRY="$OPTARG" ;; t) TAG="$OPTARG" ;; e) EMBED_MODEL="$OPTARG" ;; a) RERANK_MODEL_ID="$OPTARG" ;; @@ -94,7 +95,7 @@ log INFO " DEPLOY_MODE: $DEPLOY_MODE" log INFO " REGISTRY: ${REGISTRY:-Not set, defaults will apply in target files}" log INFO " TAG: $TAG" log INFO " MOUNT_DIR: ${MOUNT_DIR:-Not set, default to ./data}" -log INFO " EMBED_MODEL: ${EMBED_MODEL:-Not set, service default will apply}" +log INFO " EMBED_MODEL (target EMBEDDING_MODEL_ID): ${EMBED_MODEL:-Not set, service default will apply}" log INFO " RERANK_MODEL_ID: ${RERANK_MODEL_ID:-Not set, service default will apply}" log INFO " LLM_MODEL_ID: ${LLM_MODEL_ID:-Not set, service default will apply}" log INFO " HUGGINGFACEHUB_API_TOKEN: ${HUGGINGFACEHUB_API_TOKEN:+(set, not shown for security)}" @@ -104,97 +105,55 @@ log INFO " NO_PROXY: $NO_PROXY" update_yaml_image() { local file_path="$1" - local image_base_name="$2" # e.g., "opea/aisolution/chatqna" + local image_base_name="$2" local new_registry="$3" local new_tag="$4" - - # Construct the original image pattern carefully. Allows for an optional existing registry. - # Example: image: opea/aisolution/chatqna:latest - # Example: image: my.old.registry/opea/aisolution/chatqna:some-tag - # This sed tries to replace the registry and tag for a known image base name. - local new_image_prefix="" if [ -n "$new_registry" ]; then new_image_prefix="${new_registry}/" fi - - # sed -i "s|image: \(.*\/\)*${image_base_name}:.*|image: ${new_image_prefix}${image_base_name}:${new_tag}|g" "$file_path" - # A simpler approach if `opea/aisolution` is a fixed part that might be *prefixed* or *replaced* by REGISTRY - # If REGISTRY is a full prefix like "my.registry/myorg", and images are "opea/aisolution/component" - # The new image would be "my.registry/myorg/opea/aisolution/component:new_tag" - # Or if REGISTRY is "my.registry/myorg" and it *replaces* "opea", then "my.registry/myorg/aisolution/component:new_tag" - - # Assuming REGISTRY replaces "opea/aisolution" part or prefixes it. - # Let's assume images are like `opea/aisolution/image-name` and `REGISTRY` is `new-registry/new-org` - # then final image is `new-registry/new-org/image-name:TAG` - # This is complex to do reliably with sed for all YAML structures. - # A common pattern is that the default image is 'opea/aisolution/some-service:latest' - # and we want to change it to '$REGISTRY/some-service:$TAG' if REGISTRY is 'myhub.com/myteam' - # OR to '$REGISTRY/opea/aisolution/some-service:$TAG' if REGISTRY is just 'myhub.com' - - # Let's simplify: if REGISTRY is set, it replaces the part before the specific service name. - # If original is "image: opea/aisolution/chatqna:latest" - # If REGISTRY="my.repo", TAG="v2", then "image: my.repo/chatqna:v2" (assuming image_base_name includes "opea/aisolution") - # This needs a clear convention on what REGISTRY means and what image_base_name is. - - # The find command below is more general but still relies on a common original prefix. - # `find . -name '*.yaml' -type f -exec sed -i -E -e "s|image: *[^ /]+/aisolution/([^:]+):.+|image: ${REGISTRY}/\1:${TAG}|g" {} \;` - # This is too specific. - # The original `sed -i -e "/image: /s#opea/aisolution#${REGISTRY}#g" -e "/image: /s#latest#${TAG}#g" {} \;` - # assumes REGISTRY replaces "opea/aisolution". This is a strong assumption. - # A safer way for tags: - # sed -i -E "s|(image: .*:)[^:]+$|\1${new_tag}|g" "$file_path" - # And for registry, if REGISTRY is a full prefix for the image name (e.g., image_name is "chatqna", registry is "myorg"): - # sed -i -E "s|image: *([^/]+)/([^:]+):|image: ${new_registry}/\2:|g" "$file_path" - # This is very tricky. The existing find command in `update_k8s_config` is probably the most practical if assumptions hold. log WARN "YAML image update logic is complex and highly dependent on current YAML structure and REGISTRY definition. Review carefully." } function update_k8s_config() { - local k8s_manifests_base_path="${REPO_DIR}/deployment/kubernetes" + local k8s_manifests_base_path="${CHATQNA_DIR}/deployment/kubernetes" local device_manifest_dir="${k8s_manifests_base_path}/${DEVICE}" - local configmap_file_pattern="${device_manifest_dir}/chatQnA_config_map.yaml" # Assuming this naming - - # Try to find a configmap, could be named differently local configmap_file configmap_file=$(find "$device_manifest_dir" -name "*config*.yaml" -type f -print -quit) - if [ ! -f "$configmap_file" ]; then log WARN "K8s ConfigMap file not found in '$device_manifest_dir' (searched for *config*.yaml). Skipping K8s model/token config updates." else log INFO "Updating K8s ConfigMap: $configmap_file" - # Use yq if available for robust YAML editing, otherwise sed local use_yq=false if command_exists yq; then use_yq=true; fi - if [ -n "$EMBED_MODEL" ]; then - log INFO " Updating EMBED_MODEL to: $EMBED_MODEL" - if $use_yq; then yq e ".data.EMBED_MODEL = \"$EMBED_MODEL\"" -i "$configmap_file"; else \ - sed -i "s|^\([[:space:]]*EMBED_MODEL:\).*|\1 \"$EMBED_MODEL\"|" "$configmap_file"; fi + if [ -n "$EMBED_MODEL" ]; then # User param -e + log INFO " Updating EMBEDDING_MODEL_ID to: $EMBED_MODEL" # K8s ConfigMap key + if $use_yq; then yq e ".data.EMBEDDING_MODEL_ID = \"$EMBED_MODEL\"" -i "$configmap_file"; else \ + sed -i "s|^\([[:space:]]*EMBEDDING_MODEL_ID:\).*|\1 \"$EMBED_MODEL\"|" "$configmap_file"; fi fi - if [ -n "$RERANK_MODEL_ID" ]; then - log INFO " Updating RERANK_MODEL_ID to: $RERANK_MODEL_ID" + if [ -n "$RERANK_MODEL_ID" ]; then # User param -a + log INFO " Updating RERANK_MODEL_ID to: $RERANK_MODEL_ID" # K8s ConfigMap key if $use_yq; then yq e ".data.RERANK_MODEL_ID = \"$RERANK_MODEL_ID\"" -i "$configmap_file"; else \ sed -i "s|^\([[:space:]]*RERANK_MODEL_ID:\).*|\1 \"$RERANK_MODEL_ID\"|" "$configmap_file"; fi fi - if [ -n "$LLM_MODEL_ID" ]; then - log INFO " Updating LLM_MODEL_ID to: $LLM_MODEL_ID" + if [ -n "$LLM_MODEL_ID" ]; then # User param -l + log INFO " Updating LLM_MODEL_ID to: $LLM_MODEL_ID" # K8s ConfigMap key if $use_yq; then yq e ".data.LLM_MODEL_ID = \"$LLM_MODEL_ID\"" -i "$configmap_file"; else \ sed -i "s|^\([[:space:]]*LLM_MODEL_ID:\).*|\1 \"$LLM_MODEL_ID\"|" "$configmap_file"; fi fi if [ -n "$HUGGINGFACEHUB_API_TOKEN" ]; then - log INFO " Updating HUGGINGFACEHUB_API_TOKEN..." + log INFO " Updating HUGGINGFACEHUB_API_TOKEN and HF_TOKEN..." if $use_yq; then yq e ".data.HUGGINGFACEHUB_API_TOKEN = \"$HUGGINGFACEHUB_API_TOKEN\"" -i "$configmap_file" - yq e ".data.HF_TOKEN = \"$HUGGINGFACEHUB_API_TOKEN\"" -i "$configmap_file" # If HF_TOKEN is also used + yq e ".data.HF_TOKEN = \"$HUGGINGFACEHUB_API_TOKEN\"" -i "$configmap_file" else sed -i "s|^\([[:space:]]*HUGGINGFACEHUB_API_TOKEN:\).*|\1 \"$HUGGINGFACEHUB_API_TOKEN\"|" "$configmap_file" sed -i "s|^\([[:space:]]*HF_TOKEN:\).*|\1 \"$HUGGINGFACEHUB_API_TOKEN\"|" "$configmap_file" fi fi - # Add proxy settings to ConfigMap if they are defined in it if [ -n "$HTTP_PROXY" ]; then log INFO " Updating HTTP_PROXY..." if $use_yq; then yq e ".data.HTTP_PROXY = \"$HTTP_PROXY\"" -i "$configmap_file"; else \ @@ -217,23 +176,12 @@ function update_k8s_config() { log INFO "Updating images in K8s manifests under $device_manifest_dir:" [ -n "$REGISTRY" ] && log INFO " Setting registry part to: $REGISTRY" [ -n "$TAG" ] && log INFO " Setting tag to: $TAG" - - # This find and sed command is quite aggressive. It assumes all images under "opea/aisolution/" - # should be replaced by "REGISTRY/" and tag updated. - # Example: image: opea/aisolution/foo:latest -> image: my.reg/foo:newtag - # This will break if images are not from "opea/aisolution" or if REGISTRY is not just a prefix. - # Consider making this more targeted or using kustomize/helm for image overrides. find "$device_manifest_dir" -name '*.yaml' -type f -print0 | while IFS= read -r -d $'\0' file; do log INFO " Processing $file for image updates..." if [ -n "$REGISTRY" ]; then - # This sed replaces 'opea/aisolution' with the new registry. - # It assumes 'opea/aisolution' is a common prefix for images that need changing. - # e.g. image: opea/aisolution/my-service:some-tag -> image: ${REGISTRY}/my-service:some-tag sed -i -E "s|image: *([A-Za-z0-9.-]+/)*opea/aisolution/([^:]+):(.*)|image: ${REGISTRY}/\2:\3|g" "$file" fi if [ -n "$TAG" ]; then - # This sed changes the tag of any image line. - # e.g. image: something/another:latest -> image: something/another:${TAG} sed -i -E "s|(image: *[^:]+:)[^:]+$|\1${TAG}|g" "$file" fi done @@ -245,25 +193,13 @@ function update_k8s_config() { function update_docker_config() { log INFO "Preparing Docker environment variables for device: $DEVICE" - local target_compose_dir="${REPO_DIR}/deployment/docker_compose/${DEVICE}" - local set_env_script_path="${target_compose_dir}/set_env.sh" # Original script - - # Prefer .env file if it exists, otherwise modify set_env.sh - # For Docker, it's usually better to set these in a .env file read by docker-compose - # or directly in the docker-compose.yaml environment sections. - # Modifying set_env.sh directly is also an option if that's the established workflow. - - # For this review, I'll stick to exporting, assuming set_env.sh will pick them up if sourced by compose, - # or that compose file directly uses these exported vars. - # The original script just exported them, which means they need to be available when `docker compose` runs. - # This script (`set_values.sh`) is typically sourced or its output is eval'd by the caller. - # If this script is run standalone, exports won't persist for a later `docker compose` call. - # The original `install_chatqna.sh` sources `set_env.sh` from the compose dir. - # The original `one_click_chatqna.sh` calls this `set_values.sh` then `install_chatqna.sh`. - # The crucial part is that `docker_compose/${DEVICE}/set_env.sh` should correctly use these. + if [ "$DEVICE" = "gaudi" ]; then + local target_compose_dir="${CHATQNA_DIR}/docker_compose/intel/hpu/${DEVICE}" + elif [ "$DEVICE" = "xeon" ]; then + local target_compose_dir="${CHATQNA_DIR}/docker_compose/intel/cpu/${DEVICE}" + fi + local set_env_script_path="${target_compose_dir}/set_env.sh" - # Let's assume `docker_compose/${DEVICE}/set_env.sh` is the primary place to set these for Docker. - # We will modify that script. if [ ! -f "$set_env_script_path" ]; then log WARN "Docker set_env.sh script not found at '$set_env_script_path'. Skipping Docker config updates." return @@ -274,36 +210,41 @@ function update_docker_config() { local file="$1" local key="$2" local value="$3" - # If key exists, update it. If not, append it. - # Ensure value is quoted if it contains spaces or special chars, though for env vars it's usually fine. + # Ensure value is quoted for sed replacement, especially if it contains special characters like / or , + # For export lines, the value itself might need internal quotes if it contains spaces, handled by caller. + local escaped_value=$(sed 's/[&/\]/\\&/g' <<< "$value") if grep -q "export ${key}=" "$file"; then - sed -i "s|^export ${key}=.*|export ${key}=${value}|" "$file" + sed -i "s|^export ${key}=.*|export ${key}=${escaped_value}|" "$file" else - echo "export ${key}=${value}" >> "$file" + echo "export ${key}=${value}" >> "$file" # Value here should be as it's meant to be in the script fi } - # It's better if set_env.sh sources another file that we can cleanly overwrite, - # or if set_env.sh is designed to use defaults if overrides aren't present. - # For now, directly editing set_env.sh based on parameters. - if [ -n "$REGISTRY" ]; then update_line_in_file "$set_env_script_path" "REGISTRY" "$REGISTRY"; fi # Assuming set_env.sh uses REPOSITORY/REGISTRY - if [ -n "$TAG" ]; then update_line_in_file "$set_env_script_path" "IMAGE_TAG" "$TAG"; fi - - if [ -n "$HTTP_PROXY" ]; then update_line_in_file "$set_env_script_path" "HTTP_PROXY" "$HTTP_PROXY"; fi - if [ -n "$HTTPS_PROXY" ]; then update_line_in_file "$set_env_script_path" "HTTPS_PROXY" "$HTTPS_PROXY"; fi - if [ -n "$NO_PROXY" ]; then update_line_in_file "$set_env_script_path" "NO_PROXY" "\"$NO_PROXY\""; fi # Quote if it has commas - [ -n "$MOUNT_DIR" ] && update_line_in_file "$set_env_script_path" "MOUNT_DIR" "$MOUNT_DIR" - - [ -n "$EMBED_MODEL" ] && export MOSEC_EMBEDDING_MODEL_ID=$EMBED_MODEL - [ -n "$RERANK_MODEL_ID" ] && export MOSEC_RERANKING_MODEL_ID=$RERANK_MODEL_ID - [ -n "$LLM_MODEL_ID" ] && export LLM_MODEL_ID=$LLM_MODEL_ID - [ -n "$HUGGINGFACEHUB_API_TOKEN" ] && export HUGGINGFACEHUB_API_TOKEN=$HUGGINGFACEHUB_API_TOKEN + if [ -n "$REGISTRY" ]; then update_line_in_file "$set_env_script_path" "REGISTRY" "$REGISTRY"; fi + if [ -n "$TAG" ]; then update_line_in_file "$set_env_script_path" "IMAGE_TAG" "$TAG"; fi # Assuming new set_env or compose uses IMAGE_TAG + + if [ -n "$HTTP_PROXY" ]; then update_line_in_file "$set_env_script_path" "HTTP_PROXY" "\"$HTTP_PROXY\""; fi + if [ -n "$HTTPS_PROXY" ]; then update_line_in_file "$set_env_script_path" "HTTPS_PROXY" "\"$HTTPS_PROXY\""; fi + # NO_PROXY is updated. The target set_env.sh appends dynamic parts like $JAEGER_IP to this. + if [ -n "$NO_PROXY" ]; then update_line_in_file "$set_env_script_path" "no_proxy" "\"$NO_PROXY\""; fi # Target script uses lowercase 'no_proxy' + if [ -n "$MOUNT_DIR" ]; then update_line_in_file "$set_env_script_path" "MOUNT_DIR" "$MOUNT_DIR"; fi # If set_env.sh uses it + + # Update model IDs based on new variable names in the target set_env.sh + # User param -e EMBED_MODEL is for EMBEDDING_MODEL_ID + if [ -n "$EMBED_MODEL" ]; then update_line_in_file "$set_env_script_path" "EMBEDDING_MODEL_ID" "\"$EMBED_MODEL\""; fi + # User param -a RERANK_MODEL_ID is for RERANK_MODEL_ID + if [ -n "$RERANK_MODEL_ID" ]; then update_line_in_file "$set_env_script_path" "RERANK_MODEL_ID" "\"$RERANK_MODEL_ID\""; fi + # User param -l LLM_MODEL_ID is for LLM_MODEL_ID + if [ -n "$LLM_MODEL_ID" ]; then update_line_in_file "$set_env_script_path" "LLM_MODEL_ID" "\"$LLM_MODEL_ID\""; fi + + # Update HUGGINGFACEHUB_API_TOKEN and HF_TOKEN + if [ -n "$HUGGINGFACEHUB_API_TOKEN" ]; then + update_line_in_file "$set_env_script_path" "HUGGINGFACEHUB_API_TOKEN" "\"$HUGGINGFACEHUB_API_TOKEN\"" + update_line_in_file "$set_env_script_path" "HF_TOKEN" "\"$HUGGINGFACEHUB_API_TOKEN\"" # New set_env.sh also uses HF_TOKEN + fi + # Variables like LOGFLAG, INDEX_NAME etc. from new set_env.sh could be added here if parameterization is needed log INFO "Docker configurations prepared/updated in $set_env_script_path." - # The original script EXPORTED variables. This means the calling shell (e.g. one_click_chatqna) - # would need to `source` this script for those exports to be effective for subsequent `docker compose` calls. - # Alternatively, `docker compose` can use an `.env` file. - # If `install_chatqna.sh` sources the `docker_compose/$DEVICE/set_env.sh`, then modifying that file is correct. } diff --git a/ChatQnA/deployment/update_images.sh b/ChatQnA/deployment/update_images.sh index 5be6902acc..de1ec7a481 100644 --- a/ChatQnA/deployment/update_images.sh +++ b/ChatQnA/deployment/update_images.sh @@ -2,7 +2,6 @@ # Copyright (C) 2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 - set -e set -o pipefail @@ -14,7 +13,7 @@ repo_path=$(realpath "${SCRIPT_DIR_UPDATE}/../") # --- Configuration --- BUILD_CONTEXT_REL_PATH="deployment/docker_image_build" BUILD_CONTEXT_PATH="${repo_path}/${BUILD_CONTEXT_REL_PATH}" -COMPOSE_FILE="${BUILD_CONTEXT_PATH}/docker_build_compose.yaml" +COMPOSE_FILE="${BUILD_CONTEXT_PATH}/build.yaml" # Updated Compose file name LOG_DIR="${repo_path}/logs" mkdir -p "$LOG_DIR" @@ -28,34 +27,33 @@ TAG="latest" DEFAULT_REPO_TAG_BRANCH="main" OPEA_REPO_BRANCH="${DEFAULT_REPO_TAG_BRANCH}" CORE_REPO_BRANCH="${DEFAULT_REPO_TAG_BRANCH}" -#CORE_REPO_URL="https://gitee.com/intel-china/aisolution-core" CORE_REPO_URL="https://github.com/intel-innersource/frameworks.ai.enterprise-solutions-core" VLLM_FORK_REPO_URL="https://github.com/HabanaAI/vllm-fork.git" VLLM_FORK_REPO_TAG="v0.6.6.post1+Gaudi-1.20.0" VLLM_REPO_URL="https://github.com/vllm-project/vllm.git" -# VLLM version is determined dynamically later by checking out latest tag +# VLLM version for main repo is determined dynamically later by checking out latest tag (or hardcoded in function) export WORKSPACE=$repo_path # Used by compose files declare -A components components=( - ["chatqna"]="chatqna opea/aisolution/chatqna" - # ["ui-usvc"]="chatqna-ui opea/aisolution/chatqna-ui" # Assuming chatqna-ui maps to a compose service, add to YAML if needed - ["embedding-mosec"]="embedding-mosec opea/aisolution/embedding-mosec" - ["embedding-mosec-endpoint"]="embedding-mosec-endpoint opea/aisolution/embedding-mosec-endpoint" - ["dataprep"]="dataprep opea/aisolution/dataprep" - ["retriever"]="retriever opea/aisolution/retriever" - ["reranking-mosec"]="reranking-mosec opea/aisolution/reranking-mosec" - ["reranking-mosec-endpoint"]="reranking-mosec-endpoint opea/aisolution/reranking-mosec-endpoint" - ["nginx"]="nginx opea/aisolution/nginx" + ["chatqna"]="chatqna opea/chatqna" + ["chatqna-ui"]="chatqna-ui opea/chatqna-ui" + ["chatqna-conversation-ui"]="chatqna-conversation-ui opea/chatqna-conversation-ui" + ["embedding"]="embedding opea/embedding" + ["retriever"]="retriever opea/retriever" + ["reranking"]="reranking opea/reranking" + ["llm-textgen"]="llm-textgen opea/llm-textgen" + ["llm-faqgen"]="llm-faqgen opea/llm-faqgen" + ["dataprep"]="dataprep opea/dataprep" + ["guardrails"]="guardrails opea/guardrails" + ["vllm-rocm"]="vllm-rocm opea/vllm-rocm" + ["vllm"]="vllm opea/vllm" # For CPU vLLM ["vllm-gaudi"]="vllm-gaudi opea/vllm-gaudi" - ["vllm-cpu"]="vllm opea/vllm" - + ["nginx"]="nginx opea/nginx" ) -# DEFAULT_COMPONENTS_LIST will be populated dynamically from COMPOSE_FILE + DEFAULT_COMPONENTS_LIST=() -# yaml_service_names will be populated by populate_default_components_from_compose -# and used in the main script body for hints. declare -a yaml_service_names components_to_build_list=() # User specified components @@ -77,7 +75,7 @@ usage() { echo -e " --build: Build specified components (or defaults if none specified)." echo -e " --push: Push built images to the registry." echo -e " --setup-registry: Setup a local Docker registry (localhost:5000)." - echo -e " --registry : Target Docker registry (e.g., mydockerhub/myproject, docker.io)." + echo -e " --registry : Target Docker registry (e.g., mydockerhub/myproject, docker.io/myuser)." echo -e " --tag : Image tag (default: $TAG)." echo -e " --core-repo : Git URL for core components (default: $CORE_REPO_URL)." echo -e " --core-branch : Branch/tag for core components repo (default: $DEFAULT_REPO_TAG_BRANCH)." @@ -86,92 +84,72 @@ usage() { echo -e " --help: Display this help message." echo -e "" echo -e "Available components (derived from compose file if found, otherwise from script's map):" - # Dynamically list available components from the 'components' map keys local available_comp_args=("${!components[@]}") echo -e " ${available_comp_args[*]}" echo -e "" echo -e "Example: Build specific components and push to a custom registry:" - echo -e " $0 --build --push --registry my-registry.com/myorg --tag v1.0 embedding-mosec-usvc" + echo -e " $0 --build --push --registry my-registry.com/myorg --tag v1.0 embedding" exit 0 } populate_default_components_from_compose() { - # This function populates the global 'yaml_service_names' array and 'DEFAULT_COMPONENTS_LIST' - yaml_service_names=() # Clear global array for fresh population + yaml_service_names=() local parsed_successfully=false if [ ! -f "$COMPOSE_FILE" ]; then log WARN "Compose file '$COMPOSE_FILE' not found. Cannot dynamically determine default components." - # Fallback logic will be handled after this block else log INFO "Deriving default components from $COMPOSE_FILE..." if command_exists yq; then log INFO "Attempting to parse $COMPOSE_FILE with yq." - # Try yq (mikefarah/yq v4+) syntax mapfile -t yaml_service_names < <(yq eval '.services | keys | .[]' "$COMPOSE_FILE" 2>/dev/null) if [ $? -eq 0 ] && [ ${#yaml_service_names[@]} -gt 0 ]; then - log INFO "Successfully parsed service names using yq (v4+ syntax)." parsed_successfully=true else - # Clear if previous attempt failed or returned empty yaml_service_names=() - log WARN "yq (v4+ syntax) failed or returned no services. Trying alternative yq syntax (Python yq or older mikefarah/yq)." - # Try Python yq syntax (kislyuk/yq) + log WARN "yq (v4+ syntax) failed or returned no services. Trying alternative yq syntax." mapfile -t yaml_service_names < <(yq -r '.services | keys | .[]' "$COMPOSE_FILE" 2>/dev/null) if [ $? -eq 0 ] && [ ${#yaml_service_names[@]} -gt 0 ]; then - log INFO "Successfully parsed service names using yq (Python yq or older mikefarah/yq syntax)." parsed_successfully=true - else - yaml_service_names=() # Clear again - log WARN "Alternative yq syntax also failed or returned no services. Will try sed fallback." fi fi fi - # Fallback to sed if yq is not available or all yq attempts failed if ! $parsed_successfully; then - if ! command_exists yq; then - log INFO "yq command not found. Using sed to parse $COMPOSE_FILE (less robust)." - else - log INFO "All yq parsing attempts failed. Using sed to parse $COMPOSE_FILE (less robust)." - fi - # This sed command extracts service names indented by 2 spaces under a 'services:' block. + if ! command_exists yq; log INFO "yq command not found."; else log INFO "All yq parsing attempts failed."; fi + log INFO "Using sed to parse $COMPOSE_FILE (less robust)." mapfile -t yaml_service_names < <(sed -n '/^services:/,/^[^ ]/ { /^[ ]*$/d; /^services:/d; /^[ ]{2}\S[^:]*:/ { s/^[ ]{2}\([^:]*\):.*/\1/; p } }' "$COMPOSE_FILE") if [ ${#yaml_service_names[@]} -gt 0 ]; then - log INFO "Successfully parsed service names using sed." parsed_successfully=true fi fi - fi # End of if [ -f "$COMPOSE_FILE" ] + fi if ! $parsed_successfully || [ ${#yaml_service_names[@]} -eq 0 ]; then - if [ -f "$COMPOSE_FILE" ]; then # Only log this specific warning if the file existed but parsing failed - log WARN "Failed to parse services from '$COMPOSE_FILE' using all methods, or the file has no services defined under a 'services:' key." + if [ -f "$COMPOSE_FILE" ]; then + log WARN "Failed to parse services from '$COMPOSE_FILE', or it has no services defined." fi - log WARN "Falling back to all known components in the script's 'components' map as potential defaults." + log WARN "Falling back to all known components in the script's 'components' map." DEFAULT_COMPONENTS_LIST=("${!components[@]}") if [ ${#DEFAULT_COMPONENTS_LIST[@]} -eq 0 ]; then log ERROR "The 'components' map in the script is also empty. Cannot proceed." - exit 1 # Exiting here because no components can be determined. + exit 1 fi - yaml_service_names=() # Ensure it's empty if we are using fallback for DEFAULT_COMPONENTS_LIST + yaml_service_names=() return fi - # Populate DEFAULT_COMPONENTS_LIST based on successfully parsed yaml_service_names local temp_default_list=() for service_name_from_yaml in "${yaml_service_names[@]}"; do local found_arg_name="" for comp_arg_key in "${!components[@]}"; do local component_details="${components[$comp_arg_key]}" - local compose_service_name_in_map="${component_details%% *}" # Get first word - + local compose_service_name_in_map="${component_details%% *}" if [[ "$compose_service_name_in_map" == "$service_name_from_yaml" ]]; then found_arg_name="$comp_arg_key" break fi done - if [[ -n "$found_arg_name" ]]; then temp_default_list+=("$found_arg_name") else @@ -180,14 +158,11 @@ populate_default_components_from_compose() { done DEFAULT_COMPONENTS_LIST=("${temp_default_list[@]}") log INFO "Default components derived from compose file: ${DEFAULT_COMPONENTS_LIST[*]}" - if [ ${#DEFAULT_COMPONENTS_LIST[@]} -eq 0 ] && [ ${#yaml_service_names[@]} -gt 0 ]; then # If YAML had services but none mapped - log WARN "Services were found in $COMPOSE_FILE (${yaml_service_names[*]}), but none could be mapped to known component arguments for the default list." - elif [ ${#DEFAULT_COMPONENTS_LIST[@]} -eq 0 ]; then # If YAML had no services or they didn't map + if [ ${#DEFAULT_COMPONENTS_LIST[@]} -eq 0 ]; then log WARN "No services from $COMPOSE_FILE could be mapped to known component arguments. Default list is empty." fi } - setup_local_registry_func() { local local_reg_name_const="local-docker-registry" local local_reg_port_const=5000 @@ -201,37 +176,26 @@ setup_local_registry_func() { if ! $do_setup_registry_flag; then return 0; fi log INFO "Setting up local registry '$local_reg_name_const'..." - - if docker ps --format '{{.Names}}' | grep -qx "^${local_reg_name_const}$" > /dev/null; then + if docker ps --format '{{.Names}}' | grep -qx "^${local_reg_name_const}$" > /dev/null; then log INFO "Local registry '$local_reg_name_const' already running." return 0 fi - if docker ps -aq -f name="^${local_reg_name_const}$" > /dev/null; then log INFO "Starting existing stopped local registry '$local_reg_name_const'..." - if docker start "$local_reg_name_const"; then - log OK "Started existing local registry '$local_reg_name_const'." - return 0 - else - log WARN "Failed to start existing registry container. Removing and recreating." - docker rm -f "$local_reg_name_const" &>/dev/null || log WARN "Failed to remove existing container '$local_reg_name_const'." - fi + if docker start "$local_reg_name_const"; then log OK "Started existing local registry."; return 0; fi + log WARN "Failed to start existing registry. Removing and recreating." + docker rm -f "$local_reg_name_const" &>/dev/null fi - log INFO "Creating new local registry container '$local_reg_name_const' on port $local_reg_port_const..." if docker run -d -p "${local_reg_port_const}:${local_reg_port_const}" --restart always --name "$local_reg_name_const" "$local_reg_image_const"; then - log OK "Local registry '$local_reg_name_const' started successfully." - return 0 + log OK "Local registry '$local_reg_name_const' started." else log ERROR "Failed to start new local registry container '$local_reg_name_const'." - if [[ "$REGISTRY_NAME" == "localhost:${local_reg_port_const}" ]]; then - log WARN "Pushing to $REGISTRY_NAME might fail." - fi + if [[ "$REGISTRY_NAME" == "localhost:${local_reg_port_const}" ]]; then log WARN "Pushing to $REGISTRY_NAME might fail."; fi return 1 fi } - clone_or_update_repo() { local repo_url="$1" local target_path="$2" @@ -239,139 +203,73 @@ clone_or_update_repo() { local git_clone_base_flags="--config advice.detachedHead=false" log INFO "Syncing repository $repo_url ($branch_or_tag) in $target_path" - - if should_clone_repo "$repo_url" "$target_path"; then - safe_clone_repo "$repo_url" "$target_path" "$branch_or_tag" "$git_clone_base_flags" - else - update_existing_repo "$repo_url" "$target_path" "$branch_or_tag" || { - log WARN "Update failed. Re-cloning..." - safe_clone_repo "$repo_url" "$target_path" "$branch_or_tag" "$git_clone_base_flags" - } - fi -} - -should_clone_repo() { - local repo_url="$1" - local target_path="$2" - - if [ ! -d "$target_path" ] || [ ! -d "$target_path/.git" ]; then - return 0 - fi - - local current_url - if ! current_url=$(git -C "$target_path" config --get remote.origin.url); then - return 0 - fi - - if [[ "$current_url" != "$repo_url" ]]; then - log WARN "Remote URL mismatch. Forcing re-clone." - return 0 - fi - - return 1 -} - -update_existing_repo() { - local repo_url="$1" - local target_path="$2" - local branch_or_tag="$3" - - git -C "$target_path" reset --hard HEAD || return 1 - git -C "$target_path" clean -fd || return 1 - git -C "$target_path" fetch --all --prune --tags || return 1 - - if [[ "$repo_url" == "$VLLM_REPO_URL" ]]; then - checkout_vllm_tag "$target_path" + if [ ! -d "$target_path/.git" ] || [[ "$(git -C "$target_path" config --get remote.origin.url)" != "$repo_url" ]]; then + log INFO "Cloning $repo_url..." + rm -rf "$target_path" + if [[ "$repo_url" == "$VLLM_REPO_URL" ]]; then # Special handling for main vLLM repo for specific tag checkout + git clone $git_clone_base_flags --no-single-branch "$repo_url" "$target_path" || { log ERROR "Clone failed."; return 1; } + checkout_vllm_tag "$target_path" || return 1 + else + git clone $git_clone_base_flags --depth 1 --branch "$branch_or_tag" "$repo_url" "$target_path" || { log ERROR "Clone failed."; return 1; } + fi else - checkout_normal_repo "$target_path" "$branch_or_tag" + log INFO "Updating existing repo $target_path..." + ( # Subshell for safer cd and operations + cd "$target_path" || return 1 + git reset --hard HEAD || return 1 + git clean -fd || return 1 + git fetch --all --prune --tags || return 1 + if [[ "$repo_url" == "$VLLM_REPO_URL" ]]; then + checkout_vllm_tag "." || return 1 # Pass current dir + else + git checkout "$branch_or_tag" --force || return 1 + # If it's a branch (not a tag), pull updates + if ! git rev-parse --verify --quiet "refs/tags/$branch_or_tag" >/dev/null; then + log INFO "Pulling branch updates for $branch_or_tag..." + git pull --rebase || git pull || return 1 + fi + fi + ) || { log WARN "Update failed for $target_path. Re-cloning might be needed if issues persist."; return 1; } fi + log OK "Repository $repo_url synced successfully." } checkout_vllm_tag() { - local target_path="$1" - + local target_path="$1" # Expected to be the path to the vLLM repo git -C "$target_path" fetch --tags > /dev/null 2>&1 # local latest_tag=$(git -C "$target_path" describe --tags "$(git -C "$target_path" rev-list --tags --max-count=1)") - local latest_tag="v0.8.5" + local latest_tag="v0.8.5" # Keeping this as per original script, can be parameterized if needed if [ -z "$latest_tag" ]; then - log ERROR "No vLLM tags found!" + log ERROR "No vLLM tags found in $target_path!" return 1 fi - - log INFO "Checking out vLLM tag: $latest_tag" - git -C "$target_path" checkout "$latest_tag" --force -} - -checkout_normal_repo() { - local target_path="$1" - local branch_or_tag="$2" - - git -C "$target_path" checkout "$branch_or_tag" --force || return 1 - - if ! is_git_tag "$target_path" "$branch_or_tag"; then - log INFO "Pulling branch updates..." - git -C "$target_path" pull --rebase || git -C "$target_path" pull - fi -} - -safe_clone_repo() { - local repo_url="$1" - local target_path="$2" - local branch_or_tag="$3" - local base_flags="$4" - - rm -rf "$target_path" - - if [[ "$repo_url" == "$VLLM_REPO_URL" ]]; then - git clone $base_flags --no-single-branch "$repo_url" "$target_path" - checkout_vllm_tag "$target_path" - else - git clone $base_flags --depth 1 --branch "$branch_or_tag" "$repo_url" "$target_path" - fi + log INFO "Checking out vLLM tag: $latest_tag in $target_path" + git -C "$target_path" checkout "$latest_tag" --force || { log ERROR "Failed to checkout vLLM tag $latest_tag"; return 1; } } -is_git_tag() { - local target_path="$1" - local ref="$2" - git -C "$target_path" rev-parse --verify --quiet "refs/tags/$ref" >/dev/null -} - - tag_and_push_func() { if [[ "$do_push_flag" == "false" ]]; then return 0; fi local target_registry_url="$1" - # base_image_name is now the name segment that appears AFTER the registry in the final remote name - # e.g., "chatqna" or "opea/aisolution/chatqna" - local base_image_name="$2" + local base_image_name="$2" # e.g., "chatqna" or "opea/chatqna" local image_tag_to_push="$3" if [ -z "$target_registry_url" ] && [[ "$base_image_name" != */* ]]; then - # If no registry specified AND base_image_name is simple (e.g. "chatqna", not "myuser/chatqna") - # then we assume it's for official Docker Hub image, which is not what we usually do here. - # Or, if user wants to push to their Docker Hub, they should specify --registry log WARN "No registry URL specified (--registry) and base image name '$base_image_name' is simple." - log WARN "If pushing to Docker Hub, please use --registry ." - log WARN "Skipping push for ${base_image_name}:${image_tag_to_push}." + log WARN "If pushing to Docker Hub, use --registry . Skipping push for ${base_image_name}:${image_tag_to_push}." return 1 fi - # This is the name of the image that should exist locally, prepared by the aliasing step in build_images_with_compose - # e.g., "chatqna:latest" or "opea/aisolution/chatqna:latest" local local_full_image_name="${base_image_name}:${image_tag_to_push}" - - # Construct the remote image name local remote_full_image_name if [ -n "$target_registry_url" ]; then remote_full_image_name="${target_registry_url}/${base_image_name}:${image_tag_to_push}" else - # No target_registry_url, means base_image_name should be the full name (e.g. user/repo) remote_full_image_name="${base_image_name}:${image_tag_to_push}" fi - if ! docker image inspect "${local_full_image_name}" > /dev/null 2>&1; then - log WARN "Local image ${local_full_image_name} not found. Cannot tag for remote or push. Build/Aliasing might have failed." + log WARN "Local image ${local_full_image_name} not found. Cannot tag or push. Build/Aliasing might have failed." return 1 fi @@ -384,44 +282,32 @@ tag_and_push_func() { log INFO "Pushing ${remote_full_image_name}..." if ! docker push "${remote_full_image_name}"; then log ERROR "Failed to push ${remote_full_image_name}." - docker rmi "${remote_full_image_name}" > /dev/null 2>&1 || log WARN "Could not remove local tag ${remote_full_image_name} after failed push." + docker rmi "${remote_full_image_name}" > /dev/null 2>&1 # Attempt to clean up local tag return 1 - else - log OK "Successfully pushed ${remote_full_image_name}." - # Optional: docker rmi "${remote_full_image_name}" > /dev/null 2>&1 # Clean up the remote-named local tag fi + log OK "Successfully pushed ${remote_full_image_name}." return 0 } build_images_with_compose() { - local -a services_to_build_array # Use local array for services - services_to_build_array=("$@") # Capture all arguments into the array + local -a services_to_build_array=("$@") - if [[ "$do_build_flag" == "false" ]]; then - log INFO "Skipping image build (--build not specified)." - return 0 - fi - if [ ${#services_to_build_array[@]} -eq 0 ]; then - log WARN "No valid services specified for build. Skipping." - return 0 - fi + if [[ "$do_build_flag" == "false" ]]; then log INFO "Skipping image build."; return 0; fi + if [ ${#services_to_build_array[@]} -eq 0 ]; then log WARN "No valid services for build. Skipping."; return 0; fi - # --- 1. Sync Dependencies --- section_header "Syncing Dependencies into Build Context" log INFO "Build context path: ${BUILD_CONTEXT_PATH}" mkdir -p "${BUILD_CONTEXT_PATH}" if ! command_exists git; then log ERROR "'git' command not found."; return 1; fi - # Chain dependency syncing. If one fails, subsequent ones are skipped. ( \ clone_or_update_repo "https://github.com/opea-project/GenAIComps.git" "${BUILD_CONTEXT_PATH}/GenAIComps" "$OPEA_REPO_BRANCH" && \ clone_or_update_repo "$CORE_REPO_URL" "${BUILD_CONTEXT_PATH}/aisolution-core" "$CORE_REPO_BRANCH" && \ clone_or_update_repo "$VLLM_REPO_URL" "${BUILD_CONTEXT_PATH}/vllm" "main" && \ clone_or_update_repo "$VLLM_FORK_REPO_URL" "${BUILD_CONTEXT_PATH}/vllm-fork" "$VLLM_FORK_REPO_TAG" \ ) || { log ERROR "Dependency syncing failed. Aborting build."; return 1; } - log OK "Dependencies synced successfully into build context." + log OK "Dependencies synced successfully." - # --- 2. Modify Dockerfiles (Optional - consider build args instead) --- section_header "Checking Dockerfile Modifications (if needed)" if [[ "${OPEA_REPO_BRANCH}" != "main" ]]; then log INFO "Modifying Dockerfiles for GenAIComps branch: ${OPEA_REPO_BRANCH}" @@ -434,11 +320,11 @@ build_images_with_compose() { fi done fi - #local CORE_REPO_DEFAULT_MAIN_URL="https://gitee.com/intel-china/aisolution-core" # Default assumed in Dockerfiles - local CORE_REPO_DEFAULT_MAIN_URL="https://github.com/intel-innersource/frameworks.ai.enterprise-solutions-core" - if [[ "${CORE_REPO_BRANCH}" != "main" ]] || [[ "${CORE_REPO_URL}" != "${CORE_REPO_DEFAULT_MAIN_URL}" ]]; then + local CORE_REPO_DEFAULT_MAIN_URL_IN_DOCKERFILES="https://github.com/intel-innersource/frameworks.ai.enterprise-solutions-core" # Default assumed in Dockerfiles + if [[ "${CORE_REPO_BRANCH}" != "main" ]] || [[ "${CORE_REPO_URL}" != "${CORE_REPO_DEFAULT_MAIN_URL_IN_DOCKERFILES}" ]]; then log INFO "Modifying Dockerfiles for Core repo URL: ${CORE_REPO_URL}, branch: ${CORE_REPO_BRANCH}" - local OLD_STRING_CORE="RUN git clone ${CORE_REPO_DEFAULT_MAIN_URL}" # Might be fragile if Dockerfile changes + # Be careful with this string, ensure it matches exactly what's in Dockerfiles + local OLD_STRING_CORE="RUN git clone ${CORE_REPO_DEFAULT_MAIN_URL_IN_DOCKERFILES}" local NEW_STRING_CORE="RUN git clone --depth 1 --branch ${CORE_REPO_BRANCH} ${CORE_REPO_URL}" find "${BUILD_CONTEXT_PATH}" -type f \( -name "Dockerfile" -o -name "Dockerfile.*" \) -print0 | while IFS= read -r -d $'\0' file; do if grep -qF "$OLD_STRING_CORE" "$file"; then @@ -449,48 +335,36 @@ build_images_with_compose() { fi log OK "Dockerfile modification checks complete." - # --- 3. Docker Compose Build --- section_header "Building Images with Docker Compose" log INFO "Using Compose file: ${COMPOSE_FILE}" log INFO "Building services: ${services_to_build_array[*]}" log INFO "Log file: ${BUILD_LOG_FILE}" - if [ ! -f "$COMPOSE_FILE" ]; then log ERROR "Compose file not found: $COMPOSE_FILE"; return 1; fi local -a cmd_array cmd_array=(docker compose -f "${COMPOSE_FILE}" build) - # Add proxy build args if any if [ ${#DOCKER_BUILD_PROXY_ARGS_ARRAY[@]} -gt 0 ]; then cmd_array+=("${DOCKER_BUILD_PROXY_ARGS_ARRAY[@]}") fi if $do_no_cache_flag; then cmd_array+=(--no-cache); fi - cmd_array+=("${services_to_build_array[@]}") # Add services to build + cmd_array+=("${services_to_build_array[@]}") log INFO "Executing build command:" - # Log the command carefully, handling potential spaces in args by quoting for display local display_cmd="" for arg in "${cmd_array[@]}"; do display_cmd+="'$arg' "; done log INFO "$display_cmd" - # Export variables for compose file substitution, if they are used like ${VAR} in compose - export REGISTRY="${REGISTRY_NAME}" # For image: ${REGISTRY:-opea/aisolution}/... - export IMAGE_TAG="${TAG}" # For image: ...:${IMAGE_TAG:-latest} + export REGISTRY="${REGISTRY_NAME}" + export TAG="${TAG}" # Ensure TAG is exported for compose file's ${TAG:-latest} - # Execute the command - # Tee output to log file and also capture exit status local build_status=0 { "${cmd_array[@]}" 2>&1 || build_status=$?; } | tee -a "${BUILD_LOG_FILE}" - # Check the captured exit status if [ $build_status -ne 0 ]; then - log ERROR "Docker Compose build failed with status $build_status. See log: ${BUILD_LOG_FILE}" + log ERROR "Docker Compose build failed (status $build_status). See log: ${BUILD_LOG_FILE}" return 1 - else - log OK "Docker Compose build completed successfully." fi + log OK "Docker Compose build completed." - - # --- 4. Tag Built Images (to prepare for push logic) --- - # This step ensures that a local image exists with the name that tag_and_push_func will expect. section_header "Applying Local Aliases for Push Consistency" local tag_success_count=0 local tag_fail_count=0 @@ -501,38 +375,31 @@ build_images_with_compose() { local service_was_built=false for srv_in_build in "${services_to_build_array[@]}"; do - if [[ "$srv_in_build" == "$service_name_from_map" ]]; then - service_was_built=true - break - fi + if [[ "$srv_in_build" == "$service_name_from_map" ]]; then service_was_built=true; break; fi done + if ! $service_was_built; then continue; fi - if ! $service_was_built; then - continue - fi - - # Determine the image name as built by Docker Compose local built_image_by_compose - if [ -n "$REGISTRY" ]; then # Use exported REGISTRY var which was used for 'docker compose build' + # REGISTRY is the env var passed to compose, TAG is also env var. + if [ -n "$REGISTRY" ]; then built_image_by_compose="${REGISTRY}/${service_name_from_map}:${TAG}" else - built_image_by_compose="opea/aisolution/${service_name_from_map}:${TAG}" + # Default from build.yaml's image field pattern ${REGISTRY:-opea}/... + built_image_by_compose="opea/${service_name_from_map}:${TAG}" fi - # Determine the target local alias name that tag_and_push_func will expect. - # This depends on how tag_and_push_func will be called (which depends on global REGISTRY_NAME for push). local target_local_alias_base_name_for_push - if [ -n "$REGISTRY_NAME" ]; then # REGISTRY_NAME here is the global one for the upcoming push operation - target_local_alias_base_name_for_push="$service_name_from_map" # e.g., "chatqna" + # REGISTRY_NAME is the script's global var for push operation target + if [ -n "$REGISTRY_NAME" ]; then + target_local_alias_base_name_for_push="$service_name_from_map" else - target_local_alias_base_name_for_push="$base_image_name_from_map" # e.g., "opea/aisolution/chatqna" + target_local_alias_base_name_for_push="$base_image_name_from_map" # e.g. "opea/chatqna" fi local final_local_alias_for_push="${target_local_alias_base_name_for_push}:${TAG}" - if docker image inspect "$built_image_by_compose" > /dev/null 2>&1; then if [[ "$built_image_by_compose" != "$final_local_alias_for_push" ]]; then - log INFO "Aliasing ${built_image_by_compose} -> ${final_local_alias_for_push} (for push consistency)" + log INFO "Aliasing ${built_image_by_compose} -> ${final_local_alias_for_push}" if docker tag "$built_image_by_compose" "$final_local_alias_for_push"; then ((tag_success_count++)) else @@ -540,21 +407,19 @@ build_images_with_compose() { ((tag_fail_count++)) fi else - log INFO "Image ${final_local_alias_for_push} already exists (as built by compose or matches target alias name)." + log INFO "Image ${final_local_alias_for_push} already exists as expected." ((tag_success_count++)) fi else - log WARN "Could not find image '${built_image_by_compose}' supposedly built by Docker Compose for service '${service_name_from_map}'. Skipping local aliasing." + log WARN "Image '${built_image_by_compose}' (for service '${service_name_from_map}') not found after build. Skipping alias." ((tag_fail_count++)) fi done - log OK "Local aliasing for push consistency complete. Success: $tag_success_count, Failed/Skipped: $tag_fail_count." + log OK "Local aliasing complete. Success: $tag_success_count, Failed/Skipped: $tag_fail_count." if [ $tag_fail_count -gt 0 ]; then - log WARN "Some images may not have the expected local alias for push. Subsequent push might fail for these." - # return 1; # Decide if this should be a fatal error + log WARN "Some images may not have the expected local alias for push." fi - return 0 } @@ -577,7 +442,7 @@ while [ $# -gt 0 ]; do --no-cache) do_no_cache_flag=true ;; --help) usage ;; -*) log ERROR "Unknown option: $1"; usage ;; - *) components_to_build_list+=("$1") ;; # Collects component argument names + *) components_to_build_list+=("$1") ;; esac shift done @@ -588,43 +453,37 @@ fi # --- Main Logic --- section_header "Image Update Script Started" -log INFO "Repo Path: $repo_path, Build Context: $BUILD_CONTEXT_PATH" +log INFO "Repo Path: $repo_path, Build Context Root: $BUILD_CONTEXT_PATH, Compose File: $COMPOSE_FILE" log INFO "Config - Build: $do_build_flag, Push: $do_push_flag, Setup Registry: $do_setup_registry_flag, Tag: $TAG" -log INFO "Config - Registry: ${REGISTRY_NAME:-'Not set (will use compose default or opea/aisolution prefix)'}, No Cache: $do_no_cache_flag" +log INFO "Config - Registry: ${REGISTRY_NAME:-'Default (opea/ prefix or as in compose)'}, No Cache: $do_no_cache_flag" log INFO "Config - Core Repo: $CORE_REPO_URL ($CORE_REPO_BRANCH)" -log INFO "Config - OPEA Repo: ($OPEA_REPO_BRANCH)" +log INFO "Config - GenAIComps Repo: ($OPEA_REPO_BRANCH)" # OPEA_REPO_URL is hardcoded in clone_or_update_repo log INFO "Config - vLLM Fork: $VLLM_FORK_REPO_URL ($VLLM_FORK_REPO_TAG)" -# Populate DEFAULT_COMPONENTS_LIST and global yaml_service_names from docker_build_compose.yaml populate_default_components_from_compose - if ! setup_local_registry_func; then if $do_push_flag && $do_setup_registry_flag && [[ "$REGISTRY_NAME" == "localhost:"* ]] ; then log ERROR "Local registry setup failed, and it was requested for push. Aborting." exit 1 - else - log WARN "Failed to set up local registry. Continuing..." fi + log WARN "Failed to set up local registry. Continuing..." fi -# Determine components to process based on user input or defaults if [ ${#components_to_build_list[@]} -eq 0 ]; then if [ ${#DEFAULT_COMPONENTS_LIST[@]} -eq 0 ]; then - log ERROR "No specific components listed for build, and could not determine default components (e.g., from $COMPOSE_FILE or internal map was empty)." - log ERROR "Please specify components to build or ensure defaults can be determined." - usage # Show usage, which lists available components from the 'components' map + log ERROR "No specific components listed for build, and could not determine default components." + usage exit 1 fi - log INFO "No specific components listed, processing default components derived from $COMPOSE_FILE (or fallback)." + log INFO "Using default components: ${DEFAULT_COMPONENTS_LIST[*]}" components_to_build_list=("${DEFAULT_COMPONENTS_LIST[@]}") else log INFO "Processing specified components: ${components_to_build_list[*]}" fi -# Map component arguments to compose service names -services_to_process_for_compose=() # Docker compose service names -valid_component_args_for_push=() # Valid component argument names for the push loop +services_to_process_for_compose=() +valid_component_args_for_push=() for comp_arg in "${components_to_build_list[@]}"; do if [[ -n "${components[$comp_arg]}" ]]; then read -r service_name _ <<< "${components[$comp_arg]}" @@ -632,29 +491,20 @@ for comp_arg in "${components_to_build_list[@]}"; do valid_component_args_for_push+=("$comp_arg") else log WARN "Unknown component argument '$comp_arg'. Skipping." - # Check if it's a direct service name from compose file but not in our map - # These variables are reset/re-assigned in each iteration of the loop - is_direct_service=false - service_names_to_check_hint=() # Re-initialize array - - # Check if the global yaml_service_names array has elements. - # This array is populated by populate_default_components_from_compose. + local is_direct_service=false + local service_names_to_check_hint=() if [ ${#yaml_service_names[@]} -gt 0 ]; then service_names_to_check_hint=("${yaml_service_names[@]}") else - # Fallback: check against known service names from the components map itself for comp_key_for_hint in "${!components[@]}"; do service_names_to_check_hint+=("$(echo "${components[$comp_key_for_hint]}" | awk '{print $1}')") done fi - for yaml_service_name_check in "${service_names_to_check_hint[@]}"; do - if [[ "$comp_arg" == "$yaml_service_name_check" ]]; then - is_direct_service=true; break - fi + if [[ "$comp_arg" == "$yaml_service_name_check" ]]; then is_direct_service=true; break; fi done if $is_direct_service; then - log WARN "Hint: '$comp_arg' looks like a service name from $COMPOSE_FILE. To build it, ensure it has an entry in the 'components' map in this script." + log WARN "Hint: '$comp_arg' is a service name from $COMPOSE_FILE. Ensure it's mapped in the 'components' script map." fi fi done @@ -665,10 +515,7 @@ if [ ${#services_to_process_for_compose[@]} -eq 0 ]; then fi log INFO "Mapped to Docker Compose services for build: ${services_to_process_for_compose[*]}" - -# Build images if requested if $do_build_flag; then - # Pass the array of compose service names to the build function if ! build_images_with_compose "${services_to_process_for_compose[@]}"; then log ERROR "Image building process failed." exit 1 @@ -677,39 +524,22 @@ else log INFO "Skipping build phase (--build not specified)." fi -# Push images if requested if $do_push_flag; then section_header "Pushing Images" - # REGISTRY_NAME is the global target registry for push. - # tag_and_push_func has a check for empty REGISTRY_NAME if base_image_name is simple. - # For consistency, we could add a primary check here too. if [ -z "$REGISTRY_NAME" ]; then - # Check if any image to be pushed implies a namespaced image name (e.g. myuser/myimage) - # This is a bit more complex to check perfectly here without inspecting all base_names. - # The check within tag_and_push_func is more robust per image. log WARN "Target registry not specified via --registry. Push might be ambiguous or fail for non-namespaced images." - log WARN "If pushing to Docker Hub, provide --registry ." + log WARN "If pushing to Docker Hub for an organization (e.g. 'opea'), use --registry docker.io/opea or ensure image names in compose are fully qualified for Docker Hub user." fi - log INFO "Attempting to push to registry: '${REGISTRY_NAME:-"Default (e.g., Docker Hub, image name must be namespaced)"}' with tag: $TAG" + log INFO "Attempting to push to registry: '${REGISTRY_NAME:-"Default (e.g., Docker Hub, image name must be namespaced or use default 'opea/' prefix)"}' with tag: $TAG" push_failed_count=0 - # Iterate using valid_component_args_for_push which are the user-facing names that were validated for component_arg_name in "${valid_component_args_for_push[@]}"; do - # service_name_from_map: e.g., "chatqna" - # base_image_name_from_components_map: e.g., "opea/aisolution/chatqna" read -r service_name_from_map base_image_name_from_components_map <<< "${components[$component_arg_name]}" - - # Determine the base name to use for the tag_and_push_func call. - # This is the name segment that appears AFTER the registry in the final remote name. - # This variable is reset/re-assigned in each iteration of the loop base_name_for_push_call="" if [ -n "$REGISTRY_NAME" ]; then - # If a registry is specified for push, the path on that registry is just the service name. - base_name_for_push_call="$service_name_from_map" # e.g., "chatqna" + base_name_for_push_call="$service_name_from_map" else - # If no registry specified (e.g. pushing "opea/aisolution/chatqna" to Docker Hub under implicit user) - # the base_image_name itself is the full path. - base_name_for_push_call="$base_image_name_from_components_map" # e.g., "opea/aisolution/chatqna" + base_name_for_push_call="$base_image_name_from_components_map" fi log INFO "--- Pushing component: $component_arg_name (image base for push: ${base_name_for_push_call}:${TAG}) ---" @@ -721,14 +551,12 @@ if $do_push_flag; then if [ $push_failed_count -gt 0 ]; then log ERROR "$push_failed_count image(s) failed to push." - # exit 1 # Optional: exit if any push fails else - log OK "All requested images pushed successfully." + log OK "All requested images pushed successfully (or skipped as per config)." fi else log INFO "Skipping push phase (--push not specified)." fi - section_header "Image Update Script Finished Successfully" -exit 0 +exit 0 \ No newline at end of file From 9450fab5fdab19cdded52eb05a305eea3ad055e3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 06:06:45 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ChatQnA/deployment/check_env.sh | 2 +- ChatQnA/deployment/configure.sh | 4 ++-- ChatQnA/deployment/install_chatqna.sh | 2 +- ChatQnA/deployment/one_click_chatqna.sh | 6 +++--- ChatQnA/deployment/set_values.sh | 4 ++-- ChatQnA/deployment/update_images.sh | 2 +- ChatQnA/deployment/utils.sh | 1 - 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ChatQnA/deployment/check_env.sh b/ChatQnA/deployment/check_env.sh index 9b3448a8ac..9914da5cc5 100644 --- a/ChatQnA/deployment/check_env.sh +++ b/ChatQnA/deployment/check_env.sh @@ -312,4 +312,4 @@ main() { fi } -main \ No newline at end of file +main diff --git a/ChatQnA/deployment/configure.sh b/ChatQnA/deployment/configure.sh index 1409dbdb06..fdf62d10b1 100644 --- a/ChatQnA/deployment/configure.sh +++ b/ChatQnA/deployment/configure.sh @@ -6,7 +6,7 @@ set -e set -o pipefail -echo "USE WITH CAUTION THIS SCRIPT USES SUDO PRIVILAGES TO INSTALL NEEDED PACKAGES LOCALLY AND CONFIGURE THEM. \ +echo "USE WITH CAUTION THIS SCRIPT USES SUDO PRIVILEGES TO INSTALL NEEDED PACKAGES LOCALLY AND CONFIGURE THEM. \ USING IT MAY OVERWRITE EXISTING CONFIGURATION. Press ctrl+c to cancel. Sleeping for 30s." && sleep 30 usage() { @@ -92,4 +92,4 @@ fi # without error occurs: "Insufficient watch descriptors available. Reverting to -n." (in journalctl receiver) [[ $(sudo sysctl -n fs.inotify.max_user_instances) -lt 8000 ]] && sudo sysctl -w fs.inotify.max_user_instances=8192 -echo "All installations and configurations are complete." \ No newline at end of file +echo "All installations and configurations are complete." diff --git a/ChatQnA/deployment/install_chatqna.sh b/ChatQnA/deployment/install_chatqna.sh index bc57d5289a..5d09699fb2 100644 --- a/ChatQnA/deployment/install_chatqna.sh +++ b/ChatQnA/deployment/install_chatqna.sh @@ -509,4 +509,4 @@ case "$action" in esac log OK "Script finished successfully for action: $action." -exit 0 \ No newline at end of file +exit 0 diff --git a/ChatQnA/deployment/one_click_chatqna.sh b/ChatQnA/deployment/one_click_chatqna.sh index 56022578d3..dd14684a6c 100644 --- a/ChatQnA/deployment/one_click_chatqna.sh +++ b/ChatQnA/deployment/one_click_chatqna.sh @@ -53,7 +53,7 @@ while [ -z "$HUG_TOKEN" ]; do fi done -log INFO "Using Hugging Face Token: ${HUG_TOKEN:0:4}...${HUG_TOKEN: -4}" +log INFO "Using Hugging Face Token: ${HUG_TOKEN:0:4}...${HUG_TOKEN: -4}" if [ ! -f $HOME/.cache/huggingface/token ]; then log INFO 'Save Hugging Face Token into $HF_HOME/token' echo $HUG_TOKEN > $HOME/.cache/huggingface/token @@ -174,9 +174,9 @@ if [[ "$run_update_images_lower" == "y" || "$run_update_images_lower" == "yes" ] log ERROR "update_images.sh not found." exit 1 fi - + update_images_args=("--tag" "$TAG" "--build") # 默认包含 build - + # 询问是否需要 push read -p "Push images to registry? (y/N) [Default: N]: " push_images push_images_lower=$(echo "$push_images" | tr '[:upper:]' '[:lower:]') diff --git a/ChatQnA/deployment/set_values.sh b/ChatQnA/deployment/set_values.sh index 0627c7b23d..2d37668bf4 100644 --- a/ChatQnA/deployment/set_values.sh +++ b/ChatQnA/deployment/set_values.sh @@ -105,7 +105,7 @@ log INFO " NO_PROXY: $NO_PROXY" update_yaml_image() { local file_path="$1" - local image_base_name="$2" + local image_base_name="$2" local new_registry="$3" local new_tag="$4" local new_image_prefix="" @@ -258,4 +258,4 @@ else exit 1 fi -log OK "Configuration update process completed for DEVICE '$DEVICE' in DEPLOY_MODE '$DEPLOY_MODE'." \ No newline at end of file +log OK "Configuration update process completed for DEVICE '$DEVICE' in DEPLOY_MODE '$DEPLOY_MODE'." diff --git a/ChatQnA/deployment/update_images.sh b/ChatQnA/deployment/update_images.sh index de1ec7a481..0e408da706 100644 --- a/ChatQnA/deployment/update_images.sh +++ b/ChatQnA/deployment/update_images.sh @@ -559,4 +559,4 @@ else fi section_header "Image Update Script Finished Successfully" -exit 0 \ No newline at end of file +exit 0 diff --git a/ChatQnA/deployment/utils.sh b/ChatQnA/deployment/utils.sh index 41718e7022..3e7c3ea0c0 100644 --- a/ChatQnA/deployment/utils.sh +++ b/ChatQnA/deployment/utils.sh @@ -84,4 +84,3 @@ get_huggingface_token() { return 1 fi } -