343 lines
9.8 KiB
Bash
343 lines
9.8 KiB
Bash
#!/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
|
|
}
|