#!/usr/bin/env bash set -euo pipefail # --------------------------------------------------------------------------- # build-and-push.sh - Build and push service images to Scaleway registry # --------------------------------------------------------------------------- # Builds all Foxhunt service Docker images and pushes to the Scaleway # Container Registry. Supports parallel builds, selective service builds, # and sccache for faster compilation. # # Usage: # ./infra/scripts/build-and-push.sh # Build & push all # ./infra/scripts/build-and-push.sh --no-push # Build only # ./infra/scripts/build-and-push.sh --services "api-gateway trading-service" # ./infra/scripts/build-and-push.sh --parallel 3 # 3 concurrent builds # ./infra/scripts/build-and-push.sh --dry-run # Show what would run # --------------------------------------------------------------------------- REGISTRY="rg.fr-par.scw.cloud" NAMESPACE="foxhunt" GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") TIMESTAMP=$(date -u +"%Y%m%d-%H%M%S") # Services built with Dockerfile.service (--build-arg SERVICE=) STANDARD_SERVICES=( api trading-service trading-agent-service ml-training-service backtesting-service broker-gateway ) # Defaults DO_PUSH=true DRY_RUN=false PARALLEL=1 SELECTED_SERVICES=() SKIP_STANDARD=false SKIP_TRAINING=false TAG_LATEST=true usage() { cat </dev/null; then GIT_COMMIT="${GIT_COMMIT}-dirty" fi TAG_COMMIT="${GIT_COMMIT}" TAG_TS="${TIMESTAMP}" echo "=== Foxhunt Docker Build ===" echo "Registry: ${REGISTRY}/${NAMESPACE}" echo "Commit: ${TAG_COMMIT}" echo "Timestamp: ${TAG_TS}" echo "Parallel: ${PARALLEL}" echo "" # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- PASS=0 FAIL=0 FAILURES=() build_and_tag() { local image_name="$1" local dockerfile="$2" shift 2 local extra_args=("$@") local full="${REGISTRY}/${NAMESPACE}/${image_name}" local tags=("-t" "${full}:${TAG_COMMIT}" "-t" "${full}:${TAG_TS}") if [[ "$TAG_LATEST" == "true" ]]; then tags+=("-t" "${full}:latest") fi local cmd=(docker build "${tags[@]}" -f "$dockerfile" "${extra_args[@]}" .) if [[ "$DRY_RUN" == "true" ]]; then echo " [DRY] ${cmd[*]}" return 0 fi echo " Building ${image_name}..." local start start=$(date +%s) if DOCKER_BUILDKIT=1 "${cmd[@]}" 2>&1 | tail -5; then local elapsed=$(( $(date +%s) - start )) echo " [OK] ${image_name} (${elapsed}s)" (( PASS++ )) else echo " [FAIL] ${image_name}" (( FAIL++ )) FAILURES+=("${image_name}") return 1 fi } push_image() { local image_name="$1" local full="${REGISTRY}/${NAMESPACE}/${image_name}" for tag in "${TAG_COMMIT}" "${TAG_TS}" $( [[ "$TAG_LATEST" == "true" ]] && echo "latest" ); do if [[ "$DRY_RUN" == "true" ]]; then echo " [DRY] docker push ${full}:${tag}" else echo " Pushing ${full}:${tag}" docker push "${full}:${tag}" fi done } # --------------------------------------------------------------------------- # Ensure registry login # --------------------------------------------------------------------------- if [[ "$DRY_RUN" != "true" && "$DO_PUSH" == "true" ]]; then echo "--- Registry Login ---" if ! docker login "${REGISTRY}" -u nologin --password-stdin <<< "$(scw registry login -o json 2>/dev/null | python3 -c 'import sys,json; print(json.load(sys.stdin).get("password",""))' 2>/dev/null)" 2>/dev/null; then echo " Trying scw registry login..." scw registry login fi echo "" fi # --------------------------------------------------------------------------- # Build standard services (Dockerfile.service) # --------------------------------------------------------------------------- echo "--- Standard Services ---" if [[ "$PARALLEL" -gt 1 ]]; then # Parallel builds using background jobs PIDS=() for svc in "${SERVICES[@]}"; do ( build_and_tag "$svc" "infra/docker/Dockerfile.service" --build-arg "SERVICE=${svc}" ) & PIDS+=($!) # Throttle to PARALLEL concurrent jobs while [[ $(jobs -r | wc -l) -ge $PARALLEL ]]; do sleep 1 done done # Wait for all builds for pid in "${PIDS[@]}"; do wait "$pid" || (( FAIL++ )) done else for svc in "${SERVICES[@]}"; do build_and_tag "$svc" "infra/docker/Dockerfile.service" --build-arg "SERVICE=${svc}" || true done fi # --------------------------------------------------------------------------- # Build training image (Dockerfile.training) # --------------------------------------------------------------------------- if [[ "$SKIP_TRAINING" != "true" ]]; then echo "" echo "--- Training Image ---" build_and_tag "training" "infra/docker/Dockerfile.training" || true fi # --------------------------------------------------------------------------- # Push # --------------------------------------------------------------------------- if [[ "$DO_PUSH" == "true" ]]; then echo "" echo "--- Pushing Images ---" for svc in "${SERVICES[@]}"; do push_image "$svc" done if [[ "$SKIP_TRAINING" != "true" ]]; then push_image "training" fi fi # --------------------------------------------------------------------------- # Summary # --------------------------------------------------------------------------- echo "" echo "======================================================================" printf " Results: %d built, %d failed\n" "$PASS" "$FAIL" echo "======================================================================" if [[ $FAIL -gt 0 ]]; then echo "" echo " Failed:" for f in "${FAILURES[@]}"; do echo " - ${f}" done echo "" exit 1 fi echo "" echo " All images built successfully." if [[ "$DO_PUSH" == "true" && "$DRY_RUN" != "true" ]]; then echo " Pushed to: ${REGISTRY}/${NAMESPACE}/" fi exit 0