Files
ktransformers/docker/docker-utils.sh

373 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# docker-utils.sh - Shared utility functions for Docker image build and publish scripts
#
# This script provides common functions for:
# - Timestamp generation (Beijing timezone)
# - Version extraction from Docker images
# - Image name generation following naming conventions
# - Colored logging
# - Validation and error handling
#
# Usage: source docker-utils.sh
set -euo pipefail
# Color codes for logging
COLOR_RED='\033[0;31m'
COLOR_GREEN='\033[0;32m'
COLOR_YELLOW='\033[1;33m'
COLOR_BLUE='\033[0;34m'
COLOR_CYAN='\033[0;36m'
COLOR_RESET='\033[0m'
################################################################################
# Logging Functions
################################################################################
log_info() {
echo -e "${COLOR_BLUE}[INFO]${COLOR_RESET} $*"
}
log_success() {
echo -e "${COLOR_GREEN}[SUCCESS]${COLOR_RESET} $*"
}
log_warning() {
echo -e "${COLOR_YELLOW}[WARNING]${COLOR_RESET} $*"
}
log_error() {
echo -e "${COLOR_RED}[ERROR]${COLOR_RESET} $*" >&2
}
log_step() {
echo -e "\n${COLOR_CYAN}==>${COLOR_RESET} $*"
}
################################################################################
# Timestamp Functions
################################################################################
# Generate timestamp in Beijing timezone (UTC+8)
# Format: YYYYMMDDHHMMSS
# Example: 20241212143022
get_beijing_timestamp() {
# Try to use TZ environment variable approach
if date --version &>/dev/null 2>&1; then
# GNU date (Linux)
TZ='Asia/Shanghai' date '+%Y%m%d%H%M%S'
else
# BSD date (macOS)
TZ='Asia/Shanghai' date '+%Y%m%d%H%M%S'
fi
}
################################################################################
# CUDA Version Parsing
################################################################################
# Parse CUDA version to short format
# Input: 12.8.1 or 12.8 or 13.0.1
# Output: cu128 or cu130
parse_cuda_short_version() {
local cuda_version="$1"
# Extract major and minor version
local major minor
major=$(echo "$cuda_version" | cut -d. -f1)
minor=$(echo "$cuda_version" | cut -d. -f2)
# Validate
if [[ ! "$major" =~ ^[0-9]+$ ]] || [[ ! "$minor" =~ ^[0-9]+$ ]]; then
log_error "Invalid CUDA version format: $cuda_version"
log_error "Expected format: X.Y.Z (e.g., 12.8.1)"
return 1
fi
echo "cu${major}${minor}"
}
################################################################################
# Version Extraction
################################################################################
# Extract versions from built Docker image
# Input: image tag (e.g., ktransformers:temp-build-20241212)
# Output: Sets environment variables or prints to stdout
# SGLANG_VERSION=x.y.z
# KTRANSFORMERS_VERSION=x.y.z
# LLAMAFACTORY_VERSION=x.y.z
extract_versions_from_image() {
local image_tag="$1"
log_step "Extracting versions from image: $image_tag"
# Check if image exists
if ! docker image inspect "$image_tag" &>/dev/null; then
log_error "Image not found: $image_tag"
return 1
fi
# Extract versions.env file from the image
local versions_content
versions_content=$(docker run --rm "$image_tag" cat /workspace/versions.env 2>/dev/null)
if [ -z "$versions_content" ]; then
log_error "Failed to extract versions from image"
log_error "The /workspace/versions.env file may not exist in the image"
return 1
fi
# Parse and display versions
log_info "Extracted versions:"
echo "$versions_content" | while IFS= read -r line; do
log_info " $line"
done
# Output the content (caller can parse this or eval it)
echo "$versions_content"
}
# Validate that all required versions were extracted
# Input: versions string (output from extract_versions_from_image)
validate_versions() {
local versions="$1"
local all_valid=true
# Check each required version
for var in SGLANG_VERSION KTRANSFORMERS_VERSION LLAMAFACTORY_VERSION; do
local value
value=$(echo "$versions" | grep "^${var}=" | cut -d= -f2)
if [ -z "$value" ]; then
log_error "Missing version: $var"
all_valid=false
elif [ "$value" = "unknown" ]; then
log_warning "Version is 'unknown': $var"
# Don't fail, but warn user
fi
done
if [ "$all_valid" = false ]; then
return 1
fi
return 0
}
################################################################################
# Image Naming
################################################################################
# Generate standardized image name
# Input:
# $1: versions string (from extract_versions_from_image)
# $2: cuda_version (e.g., 12.8.1)
# $3: cpu_variant (e.g., x86-intel-multi)
# $4: functionality (e.g., sft_llamafactory or infer)
# $5: timestamp (optional, will generate if not provided)
# Output: Standardized image name
# Format: sglang-v{ver}_ktransformers-v{ver}_{cpu}_{gpu}_{func}_{timestamp}
generate_image_name() {
local versions="$1"
local cuda_version="$2"
local cpu_variant="$3"
local functionality="$4"
local timestamp="${5:-$(get_beijing_timestamp)}"
# Parse versions from the versions string
local sglang_ver ktrans_ver llama_ver
sglang_ver=$(echo "$versions" | grep "^SGLANG_VERSION=" | cut -d= -f2)
ktrans_ver=$(echo "$versions" | grep "^KTRANSFORMERS_VERSION=" | cut -d= -f2)
llama_ver=$(echo "$versions" | grep "^LLAMAFACTORY_VERSION=" | cut -d= -f2)
# Validate versions were extracted
if [ -z "$sglang_ver" ] || [ -z "$ktrans_ver" ] || [ -z "$llama_ver" ]; then
log_error "Failed to parse versions from input"
return 1
fi
# Parse CUDA short version
local cuda_short
cuda_short=$(parse_cuda_short_version "$cuda_version")
# Build functionality string
local func_str
if [ "$functionality" = "sft" ]; then
func_str="sft_llamafactory-v${llama_ver}"
else
func_str="infer"
fi
# Generate full image name
# Format: sglang-v{ver}_ktransformers-v{ver}_{cpu}_{gpu}_{func}_{timestamp}
local image_name
image_name="sglang-v${sglang_ver}_ktransformers-v${ktrans_ver}_${cpu_variant}_${cuda_short}_${func_str}_${timestamp}"
echo "$image_name"
}
# Generate simplified tag for DockerHub
# Input:
# $1: ktransformers_version (e.g., 0.4.3)
# $2: cuda_version (e.g., 12.8.1)
# Output: Simplified tag (e.g., v0.4.3-cu128)
generate_simplified_tag() {
local ktrans_ver="$1"
local cuda_version="$2"
local cuda_short
cuda_short=$(parse_cuda_short_version "$cuda_version")
echo "v${ktrans_ver}-${cuda_short}"
}
################################################################################
# Validation Functions
################################################################################
# Check if Docker daemon is running
check_docker_running() {
if ! docker info &>/dev/null; then
log_error "Docker daemon is not running"
log_error "Please start Docker and try again"
return 1
fi
return 0
}
# Check if user is logged into Docker registry
# Input: registry (optional, default: docker.io)
check_docker_login() {
local registry="${1:-docker.io}"
# Try to check auth by attempting a trivial operation
if ! docker login --help &>/dev/null; then
log_error "Docker CLI is not available"
return 1
fi
# Note: This is a best-effort check
# docker login status is not always easy to check programmatically
log_info "Assuming Docker login is configured"
log_info "If push fails, please run: docker login $registry"
return 0
}
# Validate CUDA version format
validate_cuda_version() {
local cuda_version="$1"
if [[ ! "$cuda_version" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
log_error "Invalid CUDA version format: $cuda_version"
log_error "Expected format: X.Y or X.Y.Z (e.g., 12.8 or 12.8.1)"
return 1
fi
return 0
}
# Check available disk space
# Input: required space in GB
check_disk_space() {
local required_gb="$1"
local output_dir="${2:-.}"
# Get available space in GB (works on Linux and macOS)
local available_kb
if df -k "$output_dir" &>/dev/null; then
available_kb=$(df -k "$output_dir" | tail -1 | awk '{print $4}')
local available_gb=$((available_kb / 1024 / 1024))
log_info "Available disk space: ${available_gb}GB"
if [ "$available_gb" -lt "$required_gb" ]; then
log_warning "Low disk space: ${available_gb}GB available, ${required_gb}GB recommended"
return 1
fi
else
log_warning "Unable to check disk space"
fi
return 0
}
# Check if file/directory exists and is writable
check_writable() {
local path="$1"
if [ -e "$path" ]; then
if [ ! -w "$path" ]; then
log_error "Path exists but is not writable: $path"
return 1
fi
else
# Try to create parent directory to test writability
local parent_dir
parent_dir=$(dirname "$path")
if [ ! -w "$parent_dir" ]; then
log_error "Parent directory is not writable: $parent_dir"
return 1
fi
fi
return 0
}
################################################################################
# Cleanup Functions
################################################################################
# Remove intermediate Docker images
cleanup_temp_images() {
local image_tag="$1"
log_step "Cleaning up temporary image: $image_tag"
if docker image inspect "$image_tag" &>/dev/null; then
docker rmi "$image_tag" &>/dev/null || true
log_success "Cleaned up temporary image"
fi
}
################################################################################
# Display Functions
################################################################################
# Display a summary box
display_summary() {
local title="$1"
shift
local lines=("$@")
local width=80
local border=$(printf '=%.0s' $(seq 1 $width))
echo ""
echo "$border"
echo " $title"
echo "$border"
for line in "${lines[@]}"; do
echo " $line"
done
echo "$border"
echo ""
}
################################################################################
# Export functions
################################################################################
# Export all functions so they can be used by scripts that source this file
export -f log_info log_success log_warning log_error log_step
export -f get_beijing_timestamp
export -f parse_cuda_short_version
export -f extract_versions_from_image validate_versions
export -f generate_image_name generate_simplified_tag
export -f check_docker_running check_docker_login validate_cuda_version
export -f check_disk_space check_writable
export -f cleanup_temp_images
export -f display_summary