diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml new file mode 100644 index 00000000..d055a2cb --- /dev/null +++ b/.github/workflows/sonarqube.yml @@ -0,0 +1,62 @@ +name: Java CI with Gradle Build Sonarqube + +on: + pull_request: + branches: + - '*' + +jobs: + sonarqube: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + + - name: Cache SonarQube packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + + - name: Secret Prod Env File Download + run: | + mkdir -p ./src/main/resources/properties + echo "${{ secrets.PROD_ENV_FILE }}" > ./src/main/resources/properties/env.properties + + - name: Secret Test Env File Download + run: | + mkdir -p ./src/test/resources/properties + echo "${{ secrets.TEST_ENV_FILE }}" > ./src/test/resources/properties/test-env.properties + + - name: Build and analyze + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + run: ./gradlew build sonar + + - name: Send SonarQube Analysis Result to Discord + env: + SONAR_PROJECT_NAME: ${{ secrets.SONAR_PROJECT_NAME }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + PR_NUMBER: ${{ github.event.pull_request.number }} + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: | + curl -H "Content-Type: application/json" \ + -d "{\"content\": \"๐Ÿ“Š **SonarQube ๋ถ„์„ ๊ฒฐ๊ณผ**\n\nํ”„๋กœ์ ํŠธ: **${SONAR_PROJECT_NAME}**\nPull Request ๋ฒˆํ˜ธ: **${PR_NUMBER}**\n๋ถ„์„ ๊ฒฐ๊ณผ ๋งํฌ: [SonarQube ๊ฒฐ๊ณผ ํ™•์ธํ•˜๊ธฐ](${SONAR_HOST_URL})\"}" \ + $DISCORD_WEBHOOK_URL diff --git a/build.gradle b/build.gradle index 8110bd33..254f1c58 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,8 @@ plugins { id 'java' id 'org.springframework.boot' version '3.3.1' id 'io.spring.dependency-management' version '1.1.5' + id "org.sonarqube" version "6.0.1.5171" + id "jacoco" } group = 'darkoverload' @@ -138,4 +140,137 @@ clean { tasks.named('test') { useJUnitPlatform() + finalizedBy jacocoTestReport +} + +sonar { + properties { + property "sonar.projectKey", "ITZipProject_itzip_back_a08e7d1d-048d-4a24-882a-266ba11904c2" + property "sonar.projectName", "itzip_back" + property "sonar.java.binaries", "$buildDir/classes/java/main" + property "sonar.test.inclusions", "**/*Test.java" + property "sonar.coverage.jacoco.xmlReportPaths", "$buildDir/reports/jacoco/test/jacocoTestReport.xml" + } +} + +// jacoco ์ •๋ณด +jacoco { + toolVersion = '0.8.11' +} + +// jacoco Report ์ƒ์„ฑ +jacocoTestReport { + dependsOn test // test ์ข…์†์„ฑ ์ถ”๊ฐ€ + + reports { + xml.required = true + html.required = true + csv.required = false + } + + def QEntityList = [ + 'darkoverload/itzip/feature/**/entity/QA*', + 'darkoverload/itzip/feature/**/entity/QB*', + 'darkoverload/itzip/feature/**/entity/QC*', + 'darkoverload/itzip/feature/**/entity/QD*', + 'darkoverload/itzip/feature/**/entity/QE*', + 'darkoverload/itzip/feature/**/entity/QF*', + 'darkoverload/itzip/feature/**/entity/QG*', + 'darkoverload/itzip/feature/**/entity/QH*', + 'darkoverload/itzip/feature/**/entity/QI*', + 'darkoverload/itzip/feature/**/entity/QJ*', + 'darkoverload/itzip/feature/**/entity/QK*', + 'darkoverload/itzip/feature/**/entity/QL*', + 'darkoverload/itzip/feature/**/entity/QM*', + 'darkoverload/itzip/feature/**/entity/QN*', + 'darkoverload/itzip/feature/**/entity/QO*', + 'darkoverload/itzip/feature/**/entity/QP*', + 'darkoverload/itzip/feature/**/entity/QQ*', + 'darkoverload/itzip/feature/**/entity/QR*', + 'darkoverload/itzip/feature/**/entity/QS*', + 'darkoverload/itzip/feature/**/entity/QT*', + 'darkoverload/itzip/feature/**/entity/QU*', + 'darkoverload/itzip/feature/**/entity/QV*', + 'darkoverload/itzip/feature/**/entity/QW*', + 'darkoverload/itzip/feature/**/entity/QX*', + 'darkoverload/itzip/feature/**/entity/QY*', + 'darkoverload/itzip/feature/**/entity/QZ*' + ] + + def QDomainList = [ + 'darkoverload/itzip/feature/**/domain/QA*', + 'darkoverload/itzip/feature/**/domain/QB*', + 'darkoverload/itzip/feature/**/domain/QC*', + 'darkoverload/itzip/feature/**/domain/QD*', + 'darkoverload/itzip/feature/**/domain/QE*', + 'darkoverload/itzip/feature/**/domain/QF*', + 'darkoverload/itzip/feature/**/domain/QG*', + 'darkoverload/itzip/feature/**/domain/QH*', + 'darkoverload/itzip/feature/**/domain/QI*', + 'darkoverload/itzip/feature/**/domain/QJ*', + 'darkoverload/itzip/feature/**/domain/QK*', + 'darkoverload/itzip/feature/**/domain/QL*', + 'darkoverload/itzip/feature/**/domain/QM*', + 'darkoverload/itzip/feature/**/domain/QN*', + 'darkoverload/itzip/feature/**/domain/QO*', + 'darkoverload/itzip/feature/**/domain/QP*', + 'darkoverload/itzip/feature/**/domain/QQ*', + 'darkoverload/itzip/feature/**/domain/QR*', + 'darkoverload/itzip/feature/**/domain/QS*', + 'darkoverload/itzip/feature/**/domain/QT*', + 'darkoverload/itzip/feature/**/domain/QU*', + 'darkoverload/itzip/feature/**/domain/QV*', + 'darkoverload/itzip/feature/**/domain/QW*', + 'darkoverload/itzip/feature/**/domain/QX*', + 'darkoverload/itzip/feature/**/domain/QY*', + 'darkoverload/itzip/feature/**/domain/QZ*' + ] + + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, exclude: [ + 'darkoverload/itzip/global/**', + 'darkoverload/itzip/mongo/**', + 'darkoverload/itzip/postgresql/**', + 'darkoverload/itzip/sample/**', + 'darkoverload/itzip/*Application*' + ] + QEntityList + QDomainList) + })) + } + + // finalizedBy 'jacocoTestCoverageVerification' // jacocoTestReport ํƒœ์Šคํฌ๊ฐ€ ๋๋‚œ ํ›„ ์‹คํ–‰ +} + +// jacoco Test ์œ ํšจ์„ฑ ํ™•์ธ +jacocoTestCoverageVerification { + def QDomainList = [] + for (qPattern in '*.QA'..'*.QZ') { // QClass ๋Œ€์‘ + QDomainList.add(qPattern + '*') + } + + violationRules { + rule { + enabled = true // ๊ทœ์น™ ํ™œ์„ฑํ™” ์—ฌ๋ถ€ + element = 'CLASS' // ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ์ฒดํฌํ•  ๋‹จ์œ„ ์„ค์ • + + // ์ฝ”๋“œ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ์ธก์ •ํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ์ง€ํ‘œ + limit { + counter = 'LINE' + value = 'COVEREDRATIO' + minimum = 0.30 + } + + limit { + counter = 'BRANCH' + value = 'COVEREDRATIO' + minimum = 0.30 + } + + excludes = [ + '**/common/**', + '**/*Application*' + ] + QDomainList + } + } + } \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml deleted file mode 100644 index b0b46121..00000000 --- a/src/main/resources/application-dev.yml +++ /dev/null @@ -1,38 +0,0 @@ -# ํ˜„์žฌ ์„ค์ • ๊ฐ’์€ local๋งŒ ์žˆ์Œ -spring: - datasource: - url: ${LOC_POSTGRESQL_URL} - username: ${LOC_POSTGRESQL_USERNAME} - password: ${LOC_POSTGRESQL_PASSWORD} - driver-class-name: org.postgresql.Driver - - data: - redis: - host: ${LOC_REDIS_HOST} - port: ${LOC_REDIS_PORT} - password: ${LOC_REDIS_PASSWORD} - - mongodb: - host: ${LOC_MONGO_HOST} - port: ${LOC_MONGO_PORT} - username: ${LOC_MONGO_USERNAME} - password: ${LOC_MONGO_PASSWORD} - database: ${LOC_MONGO_DATABASE} - authentication-database: ${LOC_MONGO_AUTH} - uri: ${LOC_MONGO_URI} - -cloud: - aws: - credentials: - instance-profile: false - accessKey: ${LOC_AWS_ACCESS_KEY} - secretKey: ${LOC_AWS_SECRET_KEY} - s3: - bucket: ${LOC_AWS_BUCKET_NAME} - region: - auto: false - static: ap-northeast-2 - stack: false - -file: - cloudfront-path: ${LOC_FILE_PATH} \ No newline at end of file diff --git a/src/test/java/darkoverload/itzip/ItzipApplicationTests.java b/src/test/java/darkoverload/itzip/ItzipApplicationTests.java index 0be6fc44..8e213a27 100644 --- a/src/test/java/darkoverload/itzip/ItzipApplicationTests.java +++ b/src/test/java/darkoverload/itzip/ItzipApplicationTests.java @@ -11,4 +11,5 @@ class ItzipApplicationTests { @Test void contextLoads() { } -} \ No newline at end of file + +}