diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1bf07c..1d328f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,30 +1,25 @@ -# This workflow will build a Java project with Gradle -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle - name: Java CI with Gradle on: push: - branches: [ main ] + branches: [ main ] pull_request: branches: [ main ] jobs: build-java: - runs-on: ubuntu-latest - #strategy: - #matrix: - # os: [ubuntu-latest, Windows-latest, macOS-latest] - steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: 1.8 + java-version: 17 + distribution: temurin - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -32,10 +27,128 @@ jobs: - name: Building with Gradle run: ./gradlew build + - name: SonarQube Analysis + timeout-minutes: 3 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + run: | + ./gradlew sonar \ + -Dsonar.projectKey=my-java-app \ + -Dsonar.host.url=$SONAR_HOST_URL \ + -Dsonar.token=$SONAR_TOKEN \ + -Dsonar.qualitygate.wait=true \ + -Dsonar.qualitygate.timeout=120 + + - name: Zip test reports + if: always() + run: | + zip -r test-reports.zip \ + build/reports/tests/ \ + build/reports/jacoco/ + + - name: Upload test report artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-reports + path: test-reports.zip + + - name: Read test results + if: always() + id: test-results + run: | + TESTS=$(find build/reports/tests/test -name "*.xml" | xargs grep -h "tests=" | grep -o 'tests="[0-9]*"' | grep -o '[0-9]*' | awk '{s+=$1} END {print s}') + FAILURES=$(find build/reports/tests/test -name "*.xml" | xargs grep -h "failures=" | grep -o 'failures="[0-9]*"' | grep -o '[0-9]*' | awk '{s+=$1} END {print s}') + SKIPPED=$(find build/reports/tests/test -name "*.xml" | xargs grep -h "skipped=" | grep -o 'skipped="[0-9]*"' | grep -o '[0-9]*' | awk '{s+=$1} END {print s}') + echo "tests=$TESTS" >> $GITHUB_OUTPUT + echo "failures=$FAILURES" >> $GITHUB_OUTPUT + echo "skipped=$SKIPPED" >> $GITHUB_OUTPUT + + - name: Get SonarQube results + if: always() + run: | + curl -u ${{ secrets.SONAR_TOKEN }}: \ + "${{ secrets.SONAR_HOST_URL }}/api/measures/component?component=my-java-app&metricKeys=bugs,vulnerabilities,code_smells,coverage,duplicated_lines_density" \ + -o sonar-results.json + cat sonar-results.json + + BUGS=$(cat sonar-results.json | python3 -c "import sys,json; measures=json.load(sys.stdin)['component']['measures']; print(next(m['value'] for m in measures if m['metric']=='bugs'))") + VULNERABILITIES=$(cat sonar-results.json | python3 -c "import sys,json; measures=json.load(sys.stdin)['component']['measures']; print(next(m['value'] for m in measures if m['metric']=='vulnerabilities'))") + CODE_SMELLS=$(cat sonar-results.json | python3 -c "import sys,json; measures=json.load(sys.stdin)['component']['measures']; print(next(m['value'] for m in measures if m['metric']=='code_smells'))") + COVERAGE=$(cat sonar-results.json | python3 -c "import sys,json; measures=json.load(sys.stdin)['component']['measures']; print(next(m['value'] for m in measures if m['metric']=='coverage'))") + + echo "bugs=$BUGS" >> $GITHUB_OUTPUT + echo "vulnerabilities=$VULNERABILITIES" >> $GITHUB_OUTPUT + echo "code_smells=$CODE_SMELLS" >> $GITHUB_OUTPUT + echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT + id: sonar-results + + - name: Send email with reports + if: always() + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 465 + username: ${{ secrets.MAIL_USERNAME }} + password: ${{ secrets.MAIL_PASSWORD }} + subject: "Build ${{ job.status }} — ${{ github.repository }} #${{ github.run_number }}" + to: muhammad.tayyab@khaleef.com + from: GitHub Actions + convert_markdown: true + body: | + ## Build Report + + | Field | Value | + |-------------|-------| + | Status | ${{ job.status }} | + | Repository | ${{ github.repository }} | + | Branch | ${{ github.ref_name }} | + | Commit | ${{ github.sha }} | + | Triggered by| ${{ github.actor }} | + | Build No | #${{ github.run_number }} | + + ## Test Results + + | Total Tests | Failures | Skipped | + |------------|----------|---------| + | ${{ steps.test-results.outputs.tests }} | ${{ steps.test-results.outputs.failures }} | ${{ steps.test-results.outputs.skipped }} | + + | Metric | Value | + |----------------|-------| + | Bugs | ${{ steps.sonar-results.outputs.bugs }} | + | Vulnerabilities| ${{ steps.sonar-results.outputs.vulnerabilities }} | + | Code Smells | ${{ steps.sonar-results.outputs.code_smells }} | + | Coverage | ${{ steps.sonar-results.outputs.coverage }}% | + + ## Links + + - [SonarQube Report](${{ secrets.SONAR_HOST_URL }}/dashboard?id=my-java-app) + - [Full Logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + - [Download Test Reports](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + - name: Build and Push Docker Image uses: mr-smithers-excellent/docker-build-push@v4 with: - image: tayyab001/githubaction - registry: docker.io - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + image: tayyab001/githubaction + tag: latest + registry: docker.io + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Deploy to server + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.DEPLOY_HOST }} + username: ${{ secrets.DEPLOY_USER }} + password: ${{ secrets.DEPLOY_PASSWORD }} + port: ${{ secrets.DEPLOY_PORT }} + script: | + docker pull tayyab001/githubaction:latest + docker stop myapp || true + docker rm myapp || true + docker run -d \ + --name myapp \ + --restart always \ + -p 8080:8080 \ + tayyab001/githubaction:latest diff --git a/build.gradle b/build.gradle index 06ee087..2eebb3d 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,8 @@ plugins { id 'java' id 'org.springframework.boot' version '2.2.2.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' + id 'org.sonarqube' version '3.5.0.2730' + id 'jacoco' } group 'com.example' @@ -15,6 +17,24 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' - compile group: 'net.logstash.logback', name: 'logstash-logback-encoder', version: '5.2' - testCompile group: 'junit', name: 'junit', version: '4.12' + implementation group: 'net.logstash.logback', name: 'logstash-logback-encoder', version: '5.2' + testImplementation group: 'junit', name: 'junit', version: '4.12' +} +sonarqube { + properties { + property 'sonar.projectKey', 'my-java-app' + property 'sonar.projectName', 'My Java App' + property 'sonar.java.source', '1.8' + property 'sonar.coverage.jacoco.xmlReportPaths', 'build/reports/jacoco/test/jacocoTestReport.xml' + } +} + +jacocoTestReport { + reports { + xml.enabled = true + } +} + +test { + finalizedBy jacocoTestReport } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6ce793f..3994438 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists