Files
git.stella-ops.org/devops/scripts/lib/ci-docker.sh

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
}