#!/bin/bash # Build CICD Images Locally - Multi-Stage Build Script # This script builds both the base and complete CICD images locally for testing set -euo pipefail # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration BASE_IMAGE_TAG="cicd-base:local" COMPLETE_IMAGE_TAG="cicd:local" PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" TEMP_SSH_KEY="/tmp/cicd_build_ssh_key" # Functions log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } cleanup() { if [[ -f "$TEMP_SSH_KEY" ]]; then rm -f "$TEMP_SSH_KEY" log_info "Cleaned up temporary SSH key" fi } trap cleanup EXIT show_usage() { cat << EOF Usage: $0 [OPTIONS] Build CICD images locally for testing Options: -h, --help Show this help message -b, --base-only Build only the base image -c, --complete-only Build only the complete image (requires base image) -f, --force Force rebuild even if images exist --no-cache Build without using Docker layer cache --ssh-key FILE Path to SSH private key (default: ~/.ssh/id_rsa) --push Push images to registry after building Examples: $0 # Build both base and complete images $0 --base-only # Build only base image $0 --complete-only # Build only complete image $0 --force --no-cache # Force rebuild with no cache EOF } check_requirements() { log_info "Checking requirements..." # Check Docker if ! command -v docker &> /dev/null; then log_error "Docker is not installed or not in PATH" exit 1 fi # Check Docker BuildKit support if ! docker buildx version &> /dev/null; then log_error "Docker BuildKit is not available" exit 1 fi # Check if we're in the right directory if [[ ! -f "$PROJECT_DIR/Dockerfile.cicd-base" ]]; then log_error "Dockerfile.cicd-base not found. Are you in the project directory?" exit 1 fi if [[ ! -f "$PROJECT_DIR/Dockerfile.cicd" ]]; then log_error "Dockerfile.cicd not found. Are you in the project directory?" exit 1 fi log_success "Requirements check passed" } build_base_image() { log_info "Building CICD base image (includes Playwright browsers - may take longer)..." log_warning "This will download ~400MB+ of browser binaries on first build" local cache_flag="" if [[ "$NO_CACHE" == "true" ]]; then cache_flag="--no-cache" fi local start_time=$(date +%s) # Calculate base Dockerfile hash for tagging local base_hash=$(sha256sum "$PROJECT_DIR/Dockerfile.cicd-base" | cut -d' ' -f1 | head -c16) log_info "Base Dockerfile hash: $base_hash" # Build base image log_info "Building base image with system dependencies and Playwright browsers..." docker build -f "$PROJECT_DIR/Dockerfile.cicd-base" \ $cache_flag \ --build-arg BASE_IMAGE_VERSION="v1.0.0-local-$base_hash" \ -t "$BASE_IMAGE_TAG" \ -t "cicd-base:$base_hash" \ "$PROJECT_DIR" local end_time=$(date +%s) local duration=$((end_time - start_time)) log_success "Base image built successfully in ${duration}s" log_info "Tagged as: $BASE_IMAGE_TAG and cicd-base:$base_hash" # Show image size local image_size=$(docker images --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}" | grep "$BASE_IMAGE_TAG" | awk '{print $2}') log_info "Base image size: $image_size (includes Playwright browsers)" }build_complete_image() { log_info "Building CICD complete image..." # Check if base image exists if ! docker image inspect "$BASE_IMAGE_TAG" &> /dev/null; then log_error "Base image $BASE_IMAGE_TAG not found. Build it first with --base-only or run without --complete-only" exit 1 fi # Check for SSH key if [[ ! -f "$SSH_KEY_PATH" ]]; then log_error "SSH key not found at $SSH_KEY_PATH" log_error "Specify path with --ssh-key or ensure ~/.ssh/id_rsa exists" exit 1 fi # Prepare SSH key for BuildKit secrets cp "$SSH_KEY_PATH" "$TEMP_SSH_KEY" chmod 600 "$TEMP_SSH_KEY" local cache_flag="" if [[ "$NO_CACHE" == "true" ]]; then cache_flag="--no-cache" fi local start_time=$(date +%s) local git_sha=$(git rev-parse HEAD 2>/dev/null || echo "unknown") log_info "Using Git SHA: $git_sha" log_info "Using SSH key: $SSH_KEY_PATH" # Build complete image DOCKER_BUILDKIT=1 docker build -f "$PROJECT_DIR/Dockerfile.cicd" \ $cache_flag \ --secret id=ssh_private_key,src="$TEMP_SSH_KEY" \ --build-arg GITHUB_SHA="$git_sha" \ --build-arg CICD_BASE_IMAGE="$BASE_IMAGE_TAG" \ -t "$COMPLETE_IMAGE_TAG" \ "$PROJECT_DIR" local end_time=$(date +%s) local duration=$((end_time - start_time)) log_success "Complete image built successfully in ${duration}s" log_info "Tagged as: $COMPLETE_IMAGE_TAG" # Show image size local image_size=$(docker images --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}" | grep "$COMPLETE_IMAGE_TAG" | awk '{print $2}') log_info "Complete image size: $image_size" } push_images() { log_warning "Push functionality not implemented yet" log_info "To push manually:" log_info " docker tag $BASE_IMAGE_TAG dogar.darkhelm.org/darkhelm.org/plex-playlist/cicd-base:latest" log_info " docker tag $COMPLETE_IMAGE_TAG dogar.darkhelm.org/darkhelm.org/plex-playlist/cicd:latest" log_info " docker push dogar.darkhelm.org/darkhelm.org/plex-playlist/cicd-base:latest" log_info " docker push dogar.darkhelm.org/darkhelm.org/plex-playlist/cicd:latest" } test_images() { log_info "Testing built images..." if [[ "$BUILD_BASE" == "true" ]] && docker image inspect "$BASE_IMAGE_TAG" &> /dev/null; then log_info "Testing base image..." if docker run --rm "$BASE_IMAGE_TAG" python3.13 --version && \ docker run --rm "$BASE_IMAGE_TAG" node --version && \ docker run --rm "$BASE_IMAGE_TAG" yarn --version && \ docker run --rm "$BASE_IMAGE_TAG" uv --version && \ docker run --rm "$BASE_IMAGE_TAG" playwright --version; then log_success "Base image tests passed (includes Playwright via npm)" # Test that browsers are installed if docker run --rm "$BASE_IMAGE_TAG" playwright install --dry-run chromium >/dev/null 2>&1; then log_success "Playwright browsers verified in base image" else log_warning "Playwright browsers may not be fully installed in base image" fi else log_error "Base image tests failed" fi fi if [[ "$BUILD_COMPLETE" == "true" ]] && docker image inspect "$COMPLETE_IMAGE_TAG" &> /dev/null; then log_info "Testing complete image..." if docker run --rm "$COMPLETE_IMAGE_TAG" bash -c "cd /workspace/backend && uv run python --version" && \ docker run --rm "$COMPLETE_IMAGE_TAG" bash -c "cd /workspace/frontend && yarn --version"; then log_success "Complete image tests passed" else log_warning "Complete image tests had issues (may be expected if frontend deps failed)" fi fi } # Default options BUILD_BASE="true" BUILD_COMPLETE="true" FORCE_BUILD="false" NO_CACHE="false" SSH_KEY_PATH="$HOME/.ssh/id_rsa" PUSH_IMAGES="false" # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_usage exit 0 ;; -b|--base-only) BUILD_BASE="true" BUILD_COMPLETE="false" shift ;; -c|--complete-only) BUILD_BASE="false" BUILD_COMPLETE="true" shift ;; -f|--force) FORCE_BUILD="true" shift ;; --no-cache) NO_CACHE="true" shift ;; --ssh-key) SSH_KEY_PATH="$2" shift 2 ;; --push) PUSH_IMAGES="true" shift ;; *) log_error "Unknown option: $1" show_usage exit 1 ;; esac done # Main execution main() { log_info "Starting CICD multi-stage build..." log_info "Project directory: $PROJECT_DIR" log_info "Build base: $BUILD_BASE" log_info "Build complete: $BUILD_COMPLETE" log_info "Force build: $FORCE_BUILD" log_info "No cache: $NO_CACHE" check_requirements # Check if images already exist (unless forced) if [[ "$FORCE_BUILD" == "false" ]]; then if [[ "$BUILD_BASE" == "true" ]] && docker image inspect "$BASE_IMAGE_TAG" &> /dev/null; then log_warning "Base image $BASE_IMAGE_TAG already exists. Use --force to rebuild." BUILD_BASE="false" fi if [[ "$BUILD_COMPLETE" == "true" ]] && docker image inspect "$COMPLETE_IMAGE_TAG" &> /dev/null; then log_warning "Complete image $COMPLETE_IMAGE_TAG already exists. Use --force to rebuild." BUILD_COMPLETE="false" fi fi # Build images if [[ "$BUILD_BASE" == "true" ]]; then build_base_image fi if [[ "$BUILD_COMPLETE" == "true" ]]; then build_complete_image fi # Test images test_images # Push if requested if [[ "$PUSH_IMAGES" == "true" ]]; then push_images fi log_success "Build process completed!" # Show final image listing log_info "Built images:" docker images | grep -E "(cicd|cicd-base)" | grep -E "(local|$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown'))" } # Run main function main "$@"