diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index a25b053..95987ef 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -1,221 +1,221 @@ -name: CI/CD Pipeline -run-name: ${{ github.actor }} triggered pipeline on ${{ github.ref_name }} - -on: - pull_request: - paths: - - 'legalconsenthub/**' - - 'legalconsenthub-backend/**' - - 'api/**' - - '.github/workflows/pipeline.yaml' - push: - branches: - - main - paths: - - 'legalconsenthub/**' - - 'legalconsenthub-backend/**' - - 'api/**' - - '.github/workflows/pipeline.yaml' - -concurrency: - group: ci-${{ github.ref }} - cancel-in-progress: true - -jobs: - frontend: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./legalconsenthub - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '22.16.0' - - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10.13.1 - run_install: false - - - name: Get pnpm store directory - id: pnpm-cache - run: | - echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - - - name: Setup pnpm cache - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build application - run: pnpm build - - - name: Run linting - run: pnpm lint - - - name: Run type checking - run: pnpm type-check - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Gitea Container Registry - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: docker/login-action@v3 - with: - registry: gitea.lugnas.de - username: ${{ github.actor }} - password: ${{ secrets.DOCKER_PUSH_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub - tags: | - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - type=sha,prefix=,format=long - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ./legalconsenthub/Dockerfile - push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - platforms: linux/amd64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - - - name: Image built successfully - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: | - echo "✅ Docker image built and pushed successfully" - echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub:latest" - echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub:${{ github.sha }}" - - - name: Dry-run completed - if: github.event_name == 'pull_request' - run: | - echo "✅ Dry-run build completed successfully (image not pushed)" - - backend: - runs-on: ubuntu-latest - - defaults: - run: - working-directory: ./legalconsenthub-backend - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - - - name: Setup Gradle cache - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - legalconsenthub-backend/.gradle - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Build application - run: ./gradlew build -x test - - - name: Run ktlint check - run: ./gradlew ktlintCheck - - - name: Run tests - run: ./gradlew test - env: - # Fixes Ryuk testcontainers error during test (https://github.com/testcontainers/testcontainers-java/issues/7036) - TESTCONTAINERS_HOST_OVERRIDE: host.docker.internal - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Gitea Container Registry - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: docker/login-action@v3 - with: - registry: gitea.lugnas.de - username: ${{ github.actor }} - password: ${{ secrets.DOCKER_PUSH_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub-backend - tags: | - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - type=sha,prefix=,format=long - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ./legalconsenthub-backend/Dockerfile - push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - platforms: linux/amd64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - - - name: Image built successfully - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: | - echo "✅ Docker image built and pushed successfully" - echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub-backend:latest" - echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub-backend:${{ github.sha }}" - - - name: Dry-run completed - if: github.event_name == 'pull_request' - run: | - echo "✅ Dry-run build completed successfully (image not pushed)" - - deploy: - runs-on: ubuntu-latest - needs: [frontend, backend] - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Deploy to server - run: | - ssh -i ~/.ssh/id_rsa -p 32766 -o StrictHostKeyChecking=accept-new deploy@ds218 "sudo /usr/local/bin/deployLegalconsenthub.sh" - - - name: Deployment successful - run: | - echo "✅ Deployment triggered successfully" - echo "🚀 Application is being deployed to production" +#name: CI/CD Pipeline +#run-name: ${{ github.actor }} triggered pipeline on ${{ github.ref_name }} +# +#on: +# pull_request: +# paths: +# - 'legalconsenthub/**' +# - 'legalconsenthub-backend/**' +# - 'api/**' +# - '.github/workflows/pipeline.yaml' +# push: +# branches: +# - main +# paths: +# - 'legalconsenthub/**' +# - 'legalconsenthub-backend/**' +# - 'api/**' +# - '.github/workflows/pipeline.yaml' +# +#concurrency: +# group: ci-${{ github.ref }} +# cancel-in-progress: true +# +#jobs: +# frontend: +# runs-on: ubuntu-latest +# defaults: +# run: +# working-directory: ./legalconsenthub +# +# steps: +# - name: Checkout code +# uses: actions/checkout@v4 +# +# - name: Setup Node.js +# uses: actions/setup-node@v4 +# with: +# node-version: '22.16.0' +# +# - name: Setup Java +# uses: actions/setup-java@v4 +# with: +# distribution: 'temurin' +# java-version: '21' +# +# - name: Setup pnpm +# uses: pnpm/action-setup@v4 +# with: +# version: 10.13.1 +# run_install: false +# +# - name: Get pnpm store directory +# id: pnpm-cache +# run: | +# echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT +# +# - name: Setup pnpm cache +# uses: actions/cache@v4 +# with: +# path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} +# key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} +# restore-keys: | +# ${{ runner.os }}-pnpm-store- +# +# - name: Install dependencies +# run: pnpm install --frozen-lockfile +# +# - name: Build application +# run: pnpm build +# +# - name: Run linting +# run: pnpm lint +# +# - name: Run type checking +# run: pnpm type-check +# +# - name: Set up Docker Buildx +# uses: docker/setup-buildx-action@v3 +# +# - name: Log in to Gitea Container Registry +# if: github.event_name == 'push' && github.ref == 'refs/heads/main' +# uses: docker/login-action@v3 +# with: +# registry: gitea.lugnas.de +# username: ${{ github.actor }} +# password: ${{ secrets.DOCKER_PUSH_TOKEN }} +# +# - name: Extract metadata for Docker +# id: meta +# uses: docker/metadata-action@v5 +# with: +# images: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub +# tags: | +# type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} +# type=sha,prefix=,format=long +# +# - name: Build and push Docker image +# uses: docker/build-push-action@v5 +# with: +# context: . +# file: ./legalconsenthub/Dockerfile +# push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} +# platforms: linux/amd64 +# tags: ${{ steps.meta.outputs.tags }} +# labels: ${{ steps.meta.outputs.labels }} +# cache-from: type=local,src=/tmp/.buildx-cache +# cache-to: type=local,dest=/tmp/.buildx-cache +# +# - name: Image built successfully +# if: github.event_name == 'push' && github.ref == 'refs/heads/main' +# run: | +# echo "✅ Docker image built and pushed successfully" +# echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub:latest" +# echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub:${{ github.sha }}" +# +# - name: Dry-run completed +# if: github.event_name == 'pull_request' +# run: | +# echo "✅ Dry-run build completed successfully (image not pushed)" +# +# backend: +# runs-on: ubuntu-latest +# +# defaults: +# run: +# working-directory: ./legalconsenthub-backend +# +# steps: +# - name: Checkout code +# uses: actions/checkout@v4 +# +# - name: Setup Java +# uses: actions/setup-java@v4 +# with: +# distribution: 'temurin' +# java-version: '21' +# +# - name: Setup Gradle cache +# uses: actions/cache@v4 +# with: +# path: | +# ~/.gradle/caches +# ~/.gradle/wrapper +# legalconsenthub-backend/.gradle +# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} +# restore-keys: | +# ${{ runner.os }}-gradle- +# +# - name: Build application +# run: ./gradlew build -x test +# +# - name: Run ktlint check +# run: ./gradlew ktlintCheck +# +# - name: Run tests +# run: ./gradlew test +# env: +# # Fixes Ryuk testcontainers error during test (https://github.com/testcontainers/testcontainers-java/issues/7036) +# TESTCONTAINERS_HOST_OVERRIDE: host.docker.internal +# +# - name: Set up Docker Buildx +# uses: docker/setup-buildx-action@v3 +# +# - name: Log in to Gitea Container Registry +# if: github.event_name == 'push' && github.ref == 'refs/heads/main' +# uses: docker/login-action@v3 +# with: +# registry: gitea.lugnas.de +# username: ${{ github.actor }} +# password: ${{ secrets.DOCKER_PUSH_TOKEN }} +# +# - name: Extract metadata for Docker +# id: meta +# uses: docker/metadata-action@v5 +# with: +# images: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub-backend +# tags: | +# type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} +# type=sha,prefix=,format=long +# +# - name: Build and push Docker image +# uses: docker/build-push-action@v5 +# with: +# context: . +# file: ./legalconsenthub-backend/Dockerfile +# push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} +# platforms: linux/amd64 +# tags: ${{ steps.meta.outputs.tags }} +# labels: ${{ steps.meta.outputs.labels }} +# cache-from: type=local,src=/tmp/.buildx-cache +# cache-to: type=local,dest=/tmp/.buildx-cache +# +# - name: Image built successfully +# if: github.event_name == 'push' && github.ref == 'refs/heads/main' +# run: | +# echo "✅ Docker image built and pushed successfully" +# echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub-backend:latest" +# echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub-backend:${{ github.sha }}" +# +# - name: Dry-run completed +# if: github.event_name == 'pull_request' +# run: | +# echo "✅ Dry-run build completed successfully (image not pushed)" +# +# deploy: +# runs-on: ubuntu-latest +# needs: [frontend, backend] +# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} +# +# steps: +# - name: Checkout code +# uses: actions/checkout@v4 +# +# - name: Deploy to server +# run: | +# ssh -i ~/.ssh/id_rsa -p 32766 -o StrictHostKeyChecking=accept-new deploy@ds218 "sudo /usr/local/bin/deployLegalconsenthub.sh" +# +# - name: Deployment successful +# run: | +# echo "✅ Deployment triggered successfully" +# echo "🚀 Application is being deployed to production" diff --git a/pipeline.sh b/pipeline.sh new file mode 100755 index 0000000..ff4d4ac --- /dev/null +++ b/pipeline.sh @@ -0,0 +1,339 @@ +#!/bin/bash + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEMP_DIR=$(mktemp -d) +DEPLOY_FLAG=false +RUN_FRONTEND=true +RUN_BACKEND=true + +cleanup() { + rm -rf "$TEMP_DIR" +} +trap cleanup EXIT + +log_info() { + echo -e "${BLUE}ℹ ${NC}$1" +} + +log_success() { + echo -e "${GREEN}✓${NC} $1" +} + +log_error() { + echo -e "${RED}✗${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}⚠${NC} $1" +} + +prefix_output() { + local prefix=$1 + local color=$2 + while IFS= read -r line; do + echo -e "${color}[${prefix}]${NC} $line" + done +} + +show_help() { + cat << EOF +Usage: $(basename "$0") [OPTIONS] + +CI/CD Pipeline Script - Runs frontend and backend builds in parallel with optional deployment + +OPTIONS: + --deploy Deploy to server after successful build + --frontend-only Run only the frontend job + --backend-only Run only the backend job + --help Show this help message + +EXAMPLES: + $(basename "$0") # Run both frontend and backend in parallel + $(basename "$0") --deploy # Run both jobs and deploy + $(basename "$0") --frontend-only # Run only frontend job + $(basename "$0") --backend-only # Run only backend job + $(basename "$0") --frontend-only --deploy # Run frontend and deploy + +ENVIRONMENT: + GIT_SHA Git commit SHA (default: current HEAD) + +EOF + exit 0 +} + +check_command() { + if ! command -v "$1" &> /dev/null; then + log_error "Required command '$1' not found. Please install it first." + exit 1 + fi +} + +check_version() { + local cmd=$1 + local expected=$2 + local version_arg=$3 + local version_output=$($cmd $version_arg 2>&1 || true) + + log_info "Checking $cmd version (expected: $expected)" + echo " Found: $version_output" +} + +validate_environment() { + log_info "Validating environment..." + + check_command "node" + check_command "pnpm" + check_command "java" + check_command "docker" + + if [ "$DEPLOY_FLAG" = true ]; then + check_command "ssh" + fi + + check_version "node" "22.16.0" "--version" + check_version "java" "21" "--version" + + if [ -z "$GIT_SHA" ]; then + GIT_SHA=$(git rev-parse HEAD 2>/dev/null || echo "unknown") + export GIT_SHA + log_info "GIT_SHA set to: $GIT_SHA" + fi + + log_success "Environment validation completed" +} + +frontend_job() { + local exit_code=0 + local log_file="$TEMP_DIR/frontend.log" + local use_prefix=$1 + + { + echo "==================== FRONTEND JOB ====================" + log_info "Starting frontend build..." + + cd "$SCRIPT_DIR/legalconsenthub" + + log_info "Installing dependencies..." + pnpm install --frozen-lockfile + log_success "Dependencies installed" + + log_info "Running linting..." + pnpm lint + log_success "Linting passed" + + log_info "Running type checking..." + pnpm type-check + log_success "Type checking passed" + + log_info "Building Docker image..." + docker buildx build \ + --platform linux/amd64 \ + --tag "gitea.lugnas.de/denis/legalconsenthub:latest" \ + --tag "gitea.lugnas.de/denis/legalconsenthub:$GIT_SHA" \ + --file ./Dockerfile \ + --push \ + .. + + log_success "Docker image built and pushed successfully" + log_info "Image: gitea.lugnas.de/denis/legalconsenthub:latest" + log_info "Image: gitea.lugnas.de/denis/legalconsenthub:$GIT_SHA" + + echo "0" > "$TEMP_DIR/frontend_exit_code" + } 2>&1 | if [ "$use_prefix" = true ]; then prefix_output "FRONTEND" "$BLUE"; else cat; fi | tee "$log_file" || { + exit_code=$? + log_error "Frontend job failed with exit code $exit_code" + echo "$exit_code" > "$TEMP_DIR/frontend_exit_code" + } +} + +backend_job() { + local exit_code=0 + local log_file="$TEMP_DIR/backend.log" + local use_prefix=$1 + + { + echo "==================== BACKEND JOB ====================" + log_info "Starting backend build..." + + cd "$SCRIPT_DIR/legalconsenthub-backend" + + log_info "Building application..." + ./gradlew build -x test + log_success "Build completed" + + log_info "Running ktlint check..." + ./gradlew ktlintCheck + log_success "ktlint check passed" + + log_info "Running tests..." + ./gradlew test + log_success "Tests passed" + + log_info "Building Docker image..." + docker buildx build \ + --platform linux/amd64 \ + --tag "gitea.lugnas.de/denis/legalconsenthub-backend:latest" \ + --tag "gitea.lugnas.de/denis/legalconsenthub-backend:$GIT_SHA" \ + --file ./Dockerfile \ + --push \ + .. + + log_success "Docker image built and pushed successfully" + log_info "Image: gitea.lugnas.de/denis/legalconsenthub-backend:latest" + log_info "Image: gitea.lugnas.de/denis/legalconsenthub-backend:$GIT_SHA" + + echo "0" > "$TEMP_DIR/backend_exit_code" + } 2>&1 | if [ "$use_prefix" = true ]; then prefix_output "BACKEND" "$GREEN"; else cat; fi | tee "$log_file" || { + exit_code=$? + log_error "Backend job failed with exit code $exit_code" + echo "$exit_code" > "$TEMP_DIR/backend_exit_code" + } +} + +run_jobs() { + local run_parallel=false + + if [ "$RUN_FRONTEND" = true ] && [ "$RUN_BACKEND" = true ]; then + run_parallel=true + log_info "Starting parallel jobs..." + else + log_info "Starting job..." + fi + echo "" + + if [ "$run_parallel" = true ]; then + frontend_job true & + local frontend_pid=$! + + backend_job true & + local backend_pid=$! + + log_info "Frontend PID: $frontend_pid" + log_info "Backend PID: $backend_pid" + log_info "Waiting for jobs to complete..." + echo "" + + wait $frontend_pid + wait $backend_pid + else + if [ "$RUN_FRONTEND" = true ]; then + frontend_job false + fi + + if [ "$RUN_BACKEND" = true ]; then + backend_job false + fi + fi + + local frontend_exit=0 + local backend_exit=0 + + if [ "$RUN_FRONTEND" = true ]; then + frontend_exit=$(cat "$TEMP_DIR/frontend_exit_code" 2>/dev/null || echo "1") + fi + + if [ "$RUN_BACKEND" = true ]; then + backend_exit=$(cat "$TEMP_DIR/backend_exit_code" 2>/dev/null || echo "1") + fi + + echo "" + echo "==================== JOB SUMMARY ====================" + + if [ "$RUN_FRONTEND" = true ]; then + if [ "$frontend_exit" -eq 0 ]; then + log_success "Frontend job completed successfully" + else + log_error "Frontend job failed with exit code $frontend_exit" + fi + fi + + if [ "$RUN_BACKEND" = true ]; then + if [ "$backend_exit" -eq 0 ]; then + log_success "Backend job completed successfully" + else + log_error "Backend job failed with exit code $backend_exit" + fi + fi + + if [ "$frontend_exit" -ne 0 ] || [ "$backend_exit" -ne 0 ]; then + log_error "One or more jobs failed. Aborting pipeline." + exit 1 + fi + + log_success "All jobs completed successfully" +} + +deploy() { + if [ "$DEPLOY_FLAG" = false ]; then + log_info "Deployment skipped (use --deploy flag to enable)" + return 0 + fi + + echo "" + echo "==================== DEPLOYMENT ====================" + log_info "Starting deployment to server..." + + ssh -F ~/.ssh/config -i ~/.ssh/gitea_deploy -p 32766 -o StrictHostKeyChecking=accept-new deploy@ds218 "sudo /usr/local/bin/deployLegalconsenthub.sh" + + log_success "Deployment triggered successfully" + log_success "Application is being deployed to production" +} + +main() { + while [[ $# -gt 0 ]]; do + case $1 in + --deploy) + DEPLOY_FLAG=true + shift + ;; + --frontend-only) + RUN_FRONTEND=true + RUN_BACKEND=false + shift + ;; + --backend-only) + RUN_FRONTEND=false + RUN_BACKEND=true + shift + ;; + --help) + show_help + ;; + *) + log_error "Unknown option: $1" + show_help + ;; + esac + done + + if [ "$RUN_FRONTEND" = false ] && [ "$RUN_BACKEND" = false ]; then + log_error "Cannot specify both --frontend-only and --backend-only" + exit 1 + fi + + echo "==================== CI/CD PIPELINE ====================" + log_info "Pipeline started at $(date)" + echo "" + + validate_environment + echo "" + + run_jobs + + deploy + + echo "" + echo "==================== PIPELINE COMPLETE ====================" + log_success "Pipeline completed successfully at $(date)" +} + +main "$@" +