diff --git a/.github/workflows/action.yaml b/.github/workflows/action.yaml new file mode 100644 index 00000000..6e67fde7 --- /dev/null +++ b/.github/workflows/action.yaml @@ -0,0 +1,204 @@ +name: Backend CI/CD + +env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PAT: ${{ secrets.DOCKER_PAT }} + IMAGE_NAME: eschool-backend + +on: + push: + branches: [ master ] + pull_request: + workflow_dispatch: + +jobs: + sonar: + name: SonarQube Scan + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'maven' + + - name: Build and Sonar Analysis + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + run: | + mvn clean verify sonar:sonar \ + -Dsonar.projectKey=eschool-backend-final-project \ + -Dsonar.host.url=$SONAR_HOST_URL \ + -Dsonar.token=$SONAR_TOKEN \ + -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} \ + -Dsonar.pullrequest.branch=${{ github.head_ref }} \ + -Dsonar.pullrequest.base=master \ + -DskipTests + + - name: SonarQube Quality Gate check + id: sonarqube-quality-gate-check + uses: sonarsource/sonarqube-quality-gate-action@master + with: + scanMetadataReportFile: target/sonar/report-task.txt + pollingTimeoutSec: 600 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + + - name: "Example show SonarQube Quality Gate Status value" + run: echo "The Quality Gate status is ${{ steps.sonarqube-quality-gate-check.outputs.quality-gate-status }}" + + - name: Get SonarQube Report (New Code + Overall) + id: sonar-report + if: github.event_name == 'pull_request' + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + run: | + NEW_CODE=$(curl -s -u "$SONAR_TOKEN:" \ + "$SONAR_HOST_URL/api/measures/component?component=eschool-backend-final-project&metricKeys=software_quality_security_rating,software_quality_reliability_rating,duplicated_lines_density,software_quality_reliability_issues,vulnerabilities,code_smells,coverage,security_hotspots&additionalFields=period") + + OVERALL=$(curl -s -u "$SONAR_TOKEN:" \ + "$SONAR_HOST_URL/api/measures/component?component=eschool-backend-final-project&metricKeys=software_quality_security_rating,software_quality_reliability_rating,duplicated_lines_density,software_quality_reliability_issues,vulnerabilities,code_smells,coverage,security_hotspots,lines,ncloc") + + echo "new_code_b64=$(echo "$NEW_CODE" | base64 -w 0)" >> $GITHUB_OUTPUT + echo "overall_b64=$(echo "$OVERALL" | base64 -w 0)" >> $GITHUB_OUTPUT + + - name: Post SonarQube results to PR + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const qualityGateStatus = '${{ steps.sonarqube-quality-gate-check.outputs.quality-gate-status }}'; + + try { + const newCodeJson = Buffer + .from('${{ steps.sonar-report.outputs.new_code_b64 }}', 'base64') + .toString(); + + const overallJson = Buffer + .from('${{ steps.sonar-report.outputs.overall_b64 }}', 'base64') + .toString(); + + const newCode = JSON.parse(newCodeJson); + const overall = JSON.parse(overallJson); + + function extractMeasures(data) { + const result = {}; + data.component.measures.forEach(m => { + result[m.metric] = { + overall: m.value ?? '0', + new: m.period?.value ?? '0' + }; + }); + return result; + } + + const newMetrics = extractMeasures(newCode); + const overallMetrics = extractMeasures(overall); + + const body = ` + ## SonarQube Analysis + + ### Quality Gate: **${qualityGateStatus}** + + ### New Code + - Security rating on new code: ${newMetrics.software_quality_security_rating?.new} + - Reliability rating on new code: ${newMetrics.software_quality_reliability_rating?.new} + - Duplicated lines density (%) on new code: ${newMetrics.duplicated_lines_density?.new} + - Bugs: ${newMetrics.software_quality_reliability_issues?.new} + - Vulnerabilities: ${newMetrics.vulnerabilities?.new} + - Code Smells: ${newMetrics.code_smells?.new} + - Coverage: ${newMetrics.coverage?.new}% + - Security hotspots on new code ${newMetrics.security_hotspots?.new} + + --- + + ### Overall Code + - Security Rating: ${overallMetrics.software_quality_security_rating?.overall} + - Reliability rating: ${overallMetrics.software_quality_reliability_rating?.overall} + - Duplicated lines density (%): ${overallMetrics.duplicated_lines_density?.overall} + - Bugs: ${overallMetrics.software_quality_reliability_issues?.overall} + - Vulnerabilities: ${overallMetrics.vulnerabilities?.overall} + - Code Smells: ${overallMetrics.code_smells?.overall} + - Coverage: ${overallMetrics.coverage?.overall}% + - Security hotspots ${overallMetrics.security_hotspots?.overall} + - Lines: ${overallMetrics.lines?.overall} + - ncloc: ${overallMetrics.ncloc?.overall} + + [View Full Report](${{ secrets.SONAR_HOST_URL }}/dashboard) + `; + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body + }); + + } catch (error) { + console.error(error); + } + + # docker-build-and-push: + # name: Build & Push Docker image + # runs-on: ubuntu-latest + # if: github.event_name != 'pull_request' + # steps: + # - uses: actions/checkout@v4 + + # - name: Docker login + # run: echo "$DOCKER_PAT" | docker login -u "$DOCKER_USERNAME" --password-stdin + + # - name: Build image + # run: | + # docker build \ + # -t $DOCKER_USERNAME/$IMAGE_NAME:backend-${GITHUB_RUN_NUMBER} \ + # -t $DOCKER_USERNAME/$IMAGE_NAME:backend-latest \ + # . + + # - name: Push image + # run: | + # docker push $DOCKER_USERNAME/$IMAGE_NAME:backend-${GITHUB_RUN_NUMBER} + # docker push $DOCKER_USERNAME/$IMAGE_NAME:backend-latest + + # deploy: + # name: Deploy Backend + # runs-on: ubuntu-latest + # needs: docker-build-and-push + # if: github.event_name != 'pull_request' + # steps: + # - name: Checkout + # uses: actions/checkout@v4 + + # - name: SSH and deploy + # env: + # PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }} + # SERVER_IP: ${{ secrets.SERVER_IP }} + # DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + # DOCKER_PAT: ${{ secrets.DOCKER_PAT }} + # IMAGE_NAME: eschool + # run: | + # echo "$PRIVATE_KEY" > private_key.pem + # chmod 600 private_key.pem + # ssh -o StrictHostKeyChecking=no -i private_key.pem ec2-user@$SERVER_IP << 'EOF' + # export DOCKER_USERNAME=${{ secrets.DOCKER_USERNAME }} + # export DOCKER_PAT=${{ secrets.DOCKER_PAT }} + # export IMAGE_NAME=eschool + + # echo "$DOCKER_PAT" | sudo docker login -u "$DOCKER_USERNAME" --password-stdin + # sudo docker pull $DOCKER_USERNAME/$IMAGE_NAME:backend-latest + + # sudo docker compose up -d --force-recreate backend + + # EOF + # rm -f private_key.pem diff --git a/Dockerfile b/Dockerfile index d0af674f..93b29191 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ -FROM maven:3.5.2-jdk-8-alpine AS MAVEN_TOOL_CHAIN +FROM maven:3.5.2-jdk-8 AS MAVEN_TOOL_CHAIN COPY pom.xml /tmp/ COPY src /tmp/src/ WORKDIR /tmp/ -RUN mvn package +RUN mvn package -DskipTests -FROM openjdk:8-jdk-alpine +FROM tomcat:9.0-jre8-alpine COPY --from=MAVEN_TOOL_CHAIN /tmp/target/eschool.jar eschool.jar -EXPOSE 8080 +EXPOSE 8081 ENTRYPOINT ["java", "-jar", "eschool.jar"] \ No newline at end of file diff --git a/pom.xml b/pom.xml index f86c7b04..09f75815 100755 --- a/pom.xml +++ b/pom.xml @@ -103,7 +103,7 @@ org.projectlombok lombok - 1.18.0 + 1.18.30 provided @@ -166,7 +166,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.6.1 + 3.11.0 1.8 1.8