Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
342
devops/scripts/lib/ci-docker.sh
Normal file
342
devops/scripts/lib/ci-docker.sh
Normal file
@@ -0,0 +1,342 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# CI DOCKER UTILITIES
|
||||
# =============================================================================
|
||||
# Docker-related utility functions for local CI testing.
|
||||
#
|
||||
# Usage:
|
||||
# source "$SCRIPT_DIR/lib/ci-docker.sh"
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Prevent multiple sourcing
|
||||
[[ -n "${_CI_DOCKER_LOADED:-}" ]] && return
|
||||
_CI_DOCKER_LOADED=1
|
||||
|
||||
# =============================================================================
|
||||
# CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
CI_COMPOSE_FILE="${CI_COMPOSE_FILE:-devops/compose/docker-compose.ci.yaml}"
|
||||
CI_IMAGE="${CI_IMAGE:-stellaops-ci:local}"
|
||||
CI_DOCKERFILE="${CI_DOCKERFILE:-devops/docker/Dockerfile.ci}"
|
||||
CI_PROJECT_NAME="${CI_PROJECT_NAME:-stellaops-ci}"
|
||||
|
||||
# Service names from docker-compose.ci.yaml
|
||||
CI_SERVICES=(postgres-ci valkey-ci nats-ci mock-registry minio-ci)
|
||||
|
||||
# =============================================================================
|
||||
# DOCKER CHECK
|
||||
# =============================================================================
|
||||
|
||||
# Check if Docker is available and running
|
||||
check_docker() {
|
||||
if ! command -v docker &>/dev/null; then
|
||||
log_error "Docker is not installed or not in PATH"
|
||||
log_info "Install Docker: https://docs.docker.com/get-docker/"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! docker info &>/dev/null; then
|
||||
log_error "Docker daemon is not running"
|
||||
log_info "Start Docker Desktop or run: sudo systemctl start docker"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_debug "Docker is available and running"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check if Docker Compose is available
|
||||
check_docker_compose() {
|
||||
if docker compose version &>/dev/null; then
|
||||
DOCKER_COMPOSE="docker compose"
|
||||
log_debug "Using Docker Compose plugin"
|
||||
return 0
|
||||
elif command -v docker-compose &>/dev/null; then
|
||||
DOCKER_COMPOSE="docker-compose"
|
||||
log_debug "Using standalone docker-compose"
|
||||
return 0
|
||||
else
|
||||
log_error "Docker Compose is not installed"
|
||||
log_info "Install with: docker compose plugin or standalone docker-compose"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CI SERVICES MANAGEMENT
|
||||
# =============================================================================
|
||||
|
||||
# Start CI services
|
||||
start_ci_services() {
|
||||
local services=("$@")
|
||||
local compose_file="$REPO_ROOT/$CI_COMPOSE_FILE"
|
||||
|
||||
if [[ ! -f "$compose_file" ]]; then
|
||||
log_error "Compose file not found: $compose_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
check_docker || return 1
|
||||
check_docker_compose || return 1
|
||||
|
||||
log_section "Starting CI Services"
|
||||
|
||||
if [[ ${#services[@]} -eq 0 ]]; then
|
||||
# Start all services
|
||||
log_info "Starting all CI services..."
|
||||
$DOCKER_COMPOSE -f "$compose_file" -p "$CI_PROJECT_NAME" up -d
|
||||
else
|
||||
# Start specific services
|
||||
log_info "Starting services: ${services[*]}"
|
||||
$DOCKER_COMPOSE -f "$compose_file" -p "$CI_PROJECT_NAME" up -d "${services[@]}"
|
||||
fi
|
||||
|
||||
local result=$?
|
||||
if [[ $result -ne 0 ]]; then
|
||||
log_error "Failed to start CI services"
|
||||
return $result
|
||||
fi
|
||||
|
||||
# Wait for services to be healthy
|
||||
wait_for_services "${services[@]}"
|
||||
}
|
||||
|
||||
# Stop CI services
|
||||
stop_ci_services() {
|
||||
local compose_file="$REPO_ROOT/$CI_COMPOSE_FILE"
|
||||
|
||||
if [[ ! -f "$compose_file" ]]; then
|
||||
log_debug "Compose file not found, nothing to stop"
|
||||
return 0
|
||||
fi
|
||||
|
||||
check_docker_compose || return 1
|
||||
|
||||
log_section "Stopping CI Services"
|
||||
|
||||
$DOCKER_COMPOSE -f "$compose_file" -p "$CI_PROJECT_NAME" down
|
||||
}
|
||||
|
||||
# Stop CI services and remove volumes
|
||||
cleanup_ci_services() {
|
||||
local compose_file="$REPO_ROOT/$CI_COMPOSE_FILE"
|
||||
|
||||
if [[ ! -f "$compose_file" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
check_docker_compose || return 1
|
||||
|
||||
log_section "Cleaning Up CI Services"
|
||||
|
||||
$DOCKER_COMPOSE -f "$compose_file" -p "$CI_PROJECT_NAME" down -v --remove-orphans
|
||||
}
|
||||
|
||||
# Check status of CI services
|
||||
check_ci_services_status() {
|
||||
local compose_file="$REPO_ROOT/$CI_COMPOSE_FILE"
|
||||
|
||||
check_docker_compose || return 1
|
||||
|
||||
log_subsection "CI Services Status"
|
||||
$DOCKER_COMPOSE -f "$compose_file" -p "$CI_PROJECT_NAME" ps
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# HEALTH CHECKS
|
||||
# =============================================================================
|
||||
|
||||
# Wait for a specific service to be healthy
|
||||
wait_for_service() {
|
||||
local service="$1"
|
||||
local timeout="${2:-60}"
|
||||
local interval="${3:-2}"
|
||||
|
||||
log_info "Waiting for $service to be healthy..."
|
||||
|
||||
local elapsed=0
|
||||
while [[ $elapsed -lt $timeout ]]; do
|
||||
local status
|
||||
status=$(docker inspect --format='{{.State.Health.Status}}' "${CI_PROJECT_NAME}-${service}-1" 2>/dev/null || echo "not found")
|
||||
|
||||
if [[ "$status" == "healthy" ]]; then
|
||||
log_success "$service is healthy"
|
||||
return 0
|
||||
elif [[ "$status" == "not found" ]]; then
|
||||
# Container might not have health check, check if running
|
||||
local running
|
||||
running=$(docker inspect --format='{{.State.Running}}' "${CI_PROJECT_NAME}-${service}-1" 2>/dev/null || echo "false")
|
||||
if [[ "$running" == "true" ]]; then
|
||||
log_success "$service is running (no health check)"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep "$interval"
|
||||
elapsed=$((elapsed + interval))
|
||||
done
|
||||
|
||||
log_error "$service did not become healthy within ${timeout}s"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Wait for multiple services to be healthy
|
||||
wait_for_services() {
|
||||
local services=("$@")
|
||||
local failed=0
|
||||
|
||||
if [[ ${#services[@]} -eq 0 ]]; then
|
||||
services=("${CI_SERVICES[@]}")
|
||||
fi
|
||||
|
||||
log_info "Waiting for services to be ready..."
|
||||
|
||||
for service in "${services[@]}"; do
|
||||
if ! wait_for_service "$service" 60 2; then
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
|
||||
return $failed
|
||||
}
|
||||
|
||||
# Check if PostgreSQL is accepting connections
|
||||
check_postgres_ready() {
|
||||
local host="${1:-localhost}"
|
||||
local port="${2:-5433}"
|
||||
local user="${3:-stellaops_ci}"
|
||||
local db="${4:-stellaops_test}"
|
||||
|
||||
if command -v pg_isready &>/dev/null; then
|
||||
pg_isready -h "$host" -p "$port" -U "$user" -d "$db" &>/dev/null
|
||||
else
|
||||
# Fallback to nc if pg_isready not available
|
||||
nc -z "$host" "$port" &>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if Valkey/Redis is accepting connections
|
||||
check_valkey_ready() {
|
||||
local host="${1:-localhost}"
|
||||
local port="${2:-6380}"
|
||||
|
||||
if command -v valkey-cli &>/dev/null; then
|
||||
valkey-cli -h "$host" -p "$port" ping &>/dev/null
|
||||
elif command -v redis-cli &>/dev/null; then
|
||||
redis-cli -h "$host" -p "$port" ping &>/dev/null
|
||||
else
|
||||
nc -z "$host" "$port" &>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CI DOCKER IMAGE MANAGEMENT
|
||||
# =============================================================================
|
||||
|
||||
# Check if CI image exists
|
||||
ci_image_exists() {
|
||||
docker image inspect "$CI_IMAGE" &>/dev/null
|
||||
}
|
||||
|
||||
# Build CI Docker image
|
||||
build_ci_image() {
|
||||
local force_rebuild="${1:-false}"
|
||||
local dockerfile="$REPO_ROOT/$CI_DOCKERFILE"
|
||||
|
||||
if [[ ! -f "$dockerfile" ]]; then
|
||||
log_error "Dockerfile not found: $dockerfile"
|
||||
return 1
|
||||
fi
|
||||
|
||||
check_docker || return 1
|
||||
|
||||
if ci_image_exists && [[ "$force_rebuild" != "true" ]]; then
|
||||
log_info "CI image already exists: $CI_IMAGE"
|
||||
log_info "Use --rebuild to force rebuild"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_section "Building CI Docker Image"
|
||||
log_info "Dockerfile: $dockerfile"
|
||||
log_info "Image: $CI_IMAGE"
|
||||
|
||||
docker build -t "$CI_IMAGE" -f "$dockerfile" "$REPO_ROOT"
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log_error "Failed to build CI image"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "CI image built successfully: $CI_IMAGE"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CONTAINER EXECUTION
|
||||
# =============================================================================
|
||||
|
||||
# Run a command inside the CI container
|
||||
run_in_ci_container() {
|
||||
local command="$*"
|
||||
|
||||
check_docker || return 1
|
||||
|
||||
if ! ci_image_exists; then
|
||||
log_info "CI image not found, building..."
|
||||
build_ci_image || return 1
|
||||
fi
|
||||
|
||||
local docker_args=(
|
||||
--rm
|
||||
-v "$REPO_ROOT:/src"
|
||||
-v "$REPO_ROOT/TestResults:/src/TestResults"
|
||||
-e DOTNET_NOLOGO=1
|
||||
-e DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
-e DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
-e TZ=UTC
|
||||
-w /src
|
||||
)
|
||||
|
||||
# Mount Docker socket for Testcontainers
|
||||
if [[ -S /var/run/docker.sock ]]; then
|
||||
docker_args+=(-v /var/run/docker.sock:/var/run/docker.sock)
|
||||
fi
|
||||
|
||||
# Load environment file if exists
|
||||
local env_file="$REPO_ROOT/devops/ci-local/.env.local"
|
||||
if [[ -f "$env_file" ]]; then
|
||||
docker_args+=(--env-file "$env_file")
|
||||
fi
|
||||
|
||||
# Connect to CI network if services are running
|
||||
if docker network inspect stellaops-ci-net &>/dev/null; then
|
||||
docker_args+=(--network stellaops-ci-net)
|
||||
fi
|
||||
|
||||
log_debug "Running in CI container: $command"
|
||||
docker run "${docker_args[@]}" "$CI_IMAGE" bash -c "$command"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# DOCKER NETWORK UTILITIES
|
||||
# =============================================================================
|
||||
|
||||
# Get the IP address of a running container
|
||||
get_container_ip() {
|
||||
local container="$1"
|
||||
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container" 2>/dev/null
|
||||
}
|
||||
|
||||
# Check if container is running
|
||||
is_container_running() {
|
||||
local container="$1"
|
||||
[[ "$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)" == "true" ]]
|
||||
}
|
||||
|
||||
# Get container logs
|
||||
get_container_logs() {
|
||||
local container="$1"
|
||||
local lines="${2:-100}"
|
||||
docker logs --tail "$lines" "$container" 2>&1
|
||||
}
|
||||
Reference in New Issue
Block a user