diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8dcdf5b6..5d739540 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,31 +1,65 @@ -name: CI +name: Java DDD Example CI (Docker) on: push: - branches: - - main + branches: [ main ] pull_request: + branches: [ main ] jobs: - build: - + build-and-test: runs-on: ubuntu-latest + timeout-minutes: 20 steps: - - uses: actions/checkout@v4 + - name: ๐Ÿ›’ Checkout code + uses: actions/checkout@v4 + + - name: ๐Ÿณ Set up Docker + run: | + sudo apt-get update + sudo apt-get install -y docker-ce docker-ce-cli containerd.io + sudo systemctl start docker - - name: ๐Ÿณ Start all the environment - run: make start + - name: ๐Ÿ—๏ธ Start containers + run: docker compose -f docker-compose.ci.yml up -d + env: + COMPOSE_DOCKER_CLI_BUILD: 1 + DOCKER_BUILDKIT: 1 - - name: ๐Ÿ”ฆ Lint - run: make lint + - name: โณ Wait for MySQL + timeout-minutes: 3 + run: | + until docker exec codely-java_ddd_example-mysql mysqladmin ping --silent; do + echo "Waiting for MySQL..." + sleep 5 + done - - name: ๐Ÿฆญ Wait for the environment to get up + - name: โณ Wait for RabbitMQ + timeout-minutes: 2 run: | - while ! make ping-mysql &>/dev/null; do - echo "Waiting for database connection..." - sleep 2 + until docker exec codely-java_ddd_example-rabbitmq rabbitmqctl await_startup; do + echo "Waiting for RabbitMQ..." + sleep 5 done - - name: โœ… Run the tests - run: make test + - name: โณ Wait for Elasticsearch + timeout-minutes: 3 + run: | + until curl -s http://localhost:9200/_cluster/health; do + echo "Waiting for Elasticsearch..." + sleep 5 + done + + - name: โœจ Code formatting check + run: docker exec codely-java_ddd_example-test_server ./gradlew spotlessCheck + + - name: ๐Ÿญ Build project + run: docker exec codely-java_ddd_example-test_server ./gradlew build --warning-mode all + + - name: ๐Ÿงช Run tests + run: docker exec codely-java_ddd_example-test_server ./gradlew test --warning-mode all + + - name: ๐Ÿงน Clean up + if: always() + run: docker compose -f docker-compose.ci.yml down diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..18115045 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,103 @@ +pipeline { + agent any + + environment { + COMPOSE_FILE = 'docker-compose.ci.yml' + NETWORK_NAME = 'ddd_network' + } + + stages { + stage('Prepare Network') { + steps { + script { + // 1. Safely handle existing network + def networkStatus = bat( + script: 'docker network inspect %NETWORK_NAME% > nul 2>&1 && echo EXISTS || echo MISSING', + returnStdout: true + ).trim() + + if (networkStatus == 'EXISTS') { + bat 'docker network rm %NETWORK_NAME%' + } + bat 'docker network create --label com.docker.compose.network=default %NETWORK_NAME%' + } + } + } + + stage('Start Services') { + steps { + // 2. Force clean start with proper network + bat """ + docker-compose -f %COMPOSE_FILE% down -v --remove-orphans + docker-compose -f %COMPOSE_FILE% build --no-cache + docker-compose -f %COMPOSE_FILE% up -d + """ + } + } + + stage('Verify Services') { + steps { + script { + // 3. Container-native checks with retries + def services = [ + [name: 'MySQL', cmd: 'docker exec mysql mysqladmin ping -uroot -p"" --silent'], + [name: 'Elasticsearch', cmd: 'curl -s http://elasticsearch:9200/_cluster/health | find "green"'], + [name: 'RabbitMQ', cmd: 'docker exec rabbitmq rabbitmqctl await_startup'] + ] + + services.each { svc -> + retry(3) { + timeout(time: 2, unit: 'MINUTES') { + waitUntil { + try { + bat(script: svc.cmd, returnStatus: true) == 0 + } catch(e) { + echo "${svc.name} check failed: ${e.message}" + bat "docker logs ${svc.name.toLowerCase()} --tail 50" + sleep 10 + false + } + } + echo "${svc.name} verified!" + } + } + } + } + } + } + + stage('Run Tests') { + steps { + bat """ + docker exec -e "SPRING_PROFILES_ACTIVE=test" \\ + test_container ./gradlew test \\ + -Dspring.datasource.url=jdbc:mysql://mysql:3306/mooc \\ + -Dspring.datasource.username=root \\ + -Dspring.datasource.password= \\ + -Delasticsearch.host=elasticsearch \\ + -Drabbitmq.host=rabbitmq + """ + } + } + } + + post { + always { + // 4. Preserve logs even if pipeline fails + script { + try { + bat """ + docker-compose -f %COMPOSE_FILE% logs --no-color > all_logs.txt + docker inspect %NETWORK_NAME% > network_info.json + """ + archiveArtifacts artifacts: '*.txt,*.json', allowEmptyArchive: true + } finally { + bat """ + docker-compose -f %COMPOSE_FILE% down -v + docker network rm %NETWORK_NAME% || echo "Network removal failed" + """ + } + } + } + } +} diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index 654ffd12..fe1c8113 100755 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -1,5 +1,3 @@ -version: '3' - services: shared_mysql: container_name: codely-java_ddd_example-mysql diff --git a/docker-compose.yml b/docker-compose.yml index 3da6fcb7..6d548137 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3' services: shared_mysql: