From f0a95f629a147f659b685a3e7742c6001a1f3088 Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 06:58:10 +0900 Subject: [PATCH 01/13] =?UTF-8?q?Jacoco=20ci=20=ED=94=8C=EB=A1=9C=EC=9A=B0?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 프로젝트 전반에 Jacoco 플러그인 적용 및 코드 커버리지 설정 추가 - SonarCloud 분석을 위한 설정 파일 작성 - CI 워크플로우에 Jacoco 리포트 생성 및 업로드 단계 추가 - 라이브러리 버전 관리 파일에 Jacoco 버전 명시 - 테스트 실행 후 코드 커버리지 리포트 생성 자동화 --- .github/workflows/commit-stage.yml | 12 ++++++ .../src/main/kotlin/lm.java-jacoco.gradle.kts | 22 ++++++++++ .../main/kotlin/lm.java-library.gradle.kts | 3 +- build.gradle.kts | 40 ++++++++++++++++++- gradle/libs.versions.toml | 3 ++ sonar-project.properties | 13 ++++++ 6 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts create mode 100644 sonar-project.properties diff --git a/.github/workflows/commit-stage.yml b/.github/workflows/commit-stage.yml index 394fce7b..81966947 100644 --- a/.github/workflows/commit-stage.yml +++ b/.github/workflows/commit-stage.yml @@ -31,6 +31,18 @@ jobs: - name: Build Project with Gradle run: ./gradlew clean build + - name: Generate Jacoco Coverage Report + run: ./gradlew jacocoAggregatedReport + + - name: Upload Jacoco Coverage Report + uses: actions/upload-artifact@v4 + with: + name: jacoco-report + path: | + build/reports/jacoco/aggregated/ + **/build/reports/jacoco/test/ + retention-days: 14 + - name: Generate and submit dependency graph uses: gradle/actions/dependency-submission@v4 with: diff --git a/build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts b/build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts new file mode 100644 index 00000000..eaad13e1 --- /dev/null +++ b/build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts @@ -0,0 +1,22 @@ +plugins { + jacoco +} + +val catalog = extensions.getByType().named("libs") + +jacoco { + toolVersion = catalog.findVersion("jacoco").get().toString() +} + +tasks.withType().configureEach { + finalizedBy(tasks.named("jacocoTestReport")) +} + +tasks.named("jacocoTestReport") { + dependsOn(tasks.withType()) + reports { + xml.required.set(true) + html.required.set(true) + csv.required.set(false) + } +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/lm.java-library.gradle.kts b/build-logic/src/main/kotlin/lm.java-library.gradle.kts index 2a55f4fc..8fca4a67 100644 --- a/build-logic/src/main/kotlin/lm.java-library.gradle.kts +++ b/build-logic/src/main/kotlin/lm.java-library.gradle.kts @@ -1,6 +1,7 @@ plugins { id("java-library") id("io.spring.dependency-management") + id("lm.java-jacoco") } group = "me.chan99k" @@ -30,4 +31,4 @@ val catalog = extensions.getByType().named("libs") dependencies { "testImplementation"(catalog.findLibrary("spring-boot-starter-test").get()) "testRuntimeOnly"(catalog.findLibrary("junit-platform-launcher").get()) -} \ No newline at end of file +} diff --git a/build.gradle.kts b/build.gradle.kts index 2c313718..f60da0d2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { java + jacoco } allprojects { @@ -9,4 +10,41 @@ allprojects { repositories { mavenCentral() } -} \ No newline at end of file +} + +jacoco { + toolVersion = libs.versions.jacoco.get() +} + +tasks.register("jacocoAggregatedReport") { + group = "verification" + description = "Generates aggregated Jacoco coverage report for all subprojects" + + val jacocoSubprojects = subprojects.filter { subproject -> + subproject.plugins.hasPlugin("jacoco") && + file("${subproject.layout.buildDirectory.get()}/jacoco/test.exec").exists() + } + + dependsOn(jacocoSubprojects.map { it.tasks.named("test") }) + + additionalSourceDirs.setFrom( + jacocoSubprojects.flatMap { it.the()["main"].allSource.srcDirs } + ) + sourceDirectories.setFrom( + jacocoSubprojects.flatMap { it.the()["main"].allSource.srcDirs } + ) + classDirectories.setFrom( + jacocoSubprojects.flatMap { it.the()["main"].output } + ) + executionData.setFrom( + jacocoSubprojects.map { file("${it.layout.buildDirectory.get()}/jacoco/test.exec") } + ) + + reports { + xml.required.set(true) + html.required.set(true) + csv.required.set(false) + xml.outputLocation.set(layout.buildDirectory.file("reports/jacoco/aggregated/jacocoTestReport.xml")) + html.outputLocation.set(layout.buildDirectory.dir("reports/jacoco/aggregated/html")) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 41315789..3422ea4e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,6 +20,9 @@ springdoc = "2.5.0" # Testing testcontainers = "1.19.0" +# Code Coverage +jacoco = "0.8.12" + [libraries] # Spring Boot Starters spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" } diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..3d358033 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,13 @@ +# SonarCloud Configuration +sonar.sources=. +sonar.sourceEncoding=UTF-8 +# Java source directories +sonar.java.source=17 +sonar.java.binaries=**/build/classes/java/main +# Jacoco coverage report paths +sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/aggregated/jacocoTestReport.xml +# Exclusions +sonar.exclusions=**/build/**,**/node_modules/**,**/*.gradle.kts +# Test sources +sonar.tests=. +sonar.test.inclusions=**/*Test.java,**/*Tests.java From 250593f56a0cc8cd96296c4c1aa452c96bf99937 Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 06:58:40 +0900 Subject: [PATCH 02/13] =?UTF-8?q?Qodana=20=EC=BD=94=EB=93=9C=20=ED=92=88?= =?UTF-8?q?=EC=A7=88=20=EB=B6=84=EC=84=9D=20=EB=B0=8F=20Gradle=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Qodana 코드 품질 분석 도구 CI 워크플로우 추가 - qodana.yaml 파일 생성 및 프로젝트 JDK, 린터 설정 - SonarCloud 워크플로우에 JDK 17 및 Gradle 설정 추가 - 코드 커버리지 리포트 생성을 위한 Gradle 명령어 구성 - actions/checkout 및 관련 액션 버전 업데이트 --- .github/workflows/qodana_code_quality.yml | 41 +++++++++++++++++++ .github/workflows/sonarcloud-analyze.yml | 20 ++++++--- qodana.yaml | 49 +++++++++++++++++++++++ 3 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/qodana_code_quality.yml create mode 100644 qodana.yaml diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml new file mode 100644 index 00000000..5dd05612 --- /dev/null +++ b/.github/workflows/qodana_code_quality.yml @@ -0,0 +1,41 @@ +#-------------------------------------------------------------------------------# +# Discover all capabilities of Qodana in our documentation # +# https://www.jetbrains.com/help/qodana/about-qodana.html # +#-------------------------------------------------------------------------------# + +name: Qodana +on: + workflow_dispatch: + pull_request: + branches: + - main + push: + branches-ignore: + - main + +jobs: + qodana: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2025.2 + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} + with: + # When pr-mode is set to true, Qodana analyzes only the files that have been changed + pr-mode: false + use-caches: true + post-pr-comment: true + use-annotations: true + # Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job + upload-result: false + # quick-fixes available in Ultimate and Ultimate Plus plans + push-fixes: 'none' diff --git a/.github/workflows/sonarcloud-analyze.yml b/.github/workflows/sonarcloud-analyze.yml index 25522d90..18b66f4f 100644 --- a/.github/workflows/sonarcloud-analyze.yml +++ b/.github/workflows/sonarcloud-analyze.yml @@ -5,24 +5,34 @@ on: types: [opened, synchronize, reopened] workflow_dispatch: -env: - CACHED_DEPENDENCIES_PATHS: '**/node_modules' - jobs: CodeAnalyze: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - 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: gradle + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build and Generate Jacoco Report + run: ./gradlew clean build jacocoAggregatedReport + - name: Set SonarCloud Project Key run: | REPO_NAME=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 2) ORG_NAME=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 1) SONAR_PROJECT_KEY="${ORG_NAME}_${REPO_NAME}" - echo "SONAR_PROJECT_KEY=$SONAR_PROJECT_KEY" >> $GITHUB_ENV + echo "SONAR_PROJECT_KEY=$SONAR_PROJECT_KEY" >> $GITHUB_ENV - name: Analyze with SonarCloud uses: SonarSource/sonarcloud-github-action@master diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 00000000..c59d901b --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,49 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# + +################################################################################# +# WARNING: Do not store sensitive information in this file, # +# as its contents will be included in the Qodana report. # +################################################################################# +version: "1.0" + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +#Enable inspections +#include: +# - name: + +#Disable inspections +#exclude: +# - name: +# paths: +# - + +projectJDK: "17" #(Applied in CI/CD pipeline) + + #Execute shell command before Qodana execution (Applied in CI/CD pipeline) + #bootstrap: sh ./prepare-qodana.sh + + #Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) + #plugins: + # - id: #(plugin id can be found at https://plugins.jetbrains.com) + + # Quality gate. Will fail the CI/CD pipeline if any condition is not met + # severityThresholds - configures maximum thresholds for different problem severities + # testCoverageThresholds - configures minimum code coverage on a whole project and newly added code + # Code Coverage is available in Ultimate and Ultimate Plus plans + #failureConditions: + # severityThresholds: + # any: 15 + # critical: 5 + # testCoverageThresholds: + # fresh: 70 + # total: 50 + +#Qodana supports other languages, for example, Python, JavaScript, TypeScript, Go, C#, PHP +#For all supported languages see https://www.jetbrains.com/help/qodana/linters.html +linter: jetbrains/qodana-jvm-community:2025.2 From 60feee96504a53f0a403337d63227997c36b0b15 Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 07:10:39 +0900 Subject: [PATCH 03/13] =?UTF-8?q?SonarCloud=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20Jacoco=20=EC=BD=94=EB=93=9C=20=EC=BB=A4=EB=B2=84?= =?UTF-8?q?=EB=A6=AC=EC=A7=80=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SonarCloud 분석 대상 소스 경로 명시적으로 수정 - Jacoco 커버리지 집계 시 서브프로젝트 test 태스크 의존성 및 exec 파일 처리 로직 개선 - Jacoco 플러그인 적용 서브프로젝트의 소스 및 클래스 경로 처리 방식 수정 --- .../src/main/kotlin/lm.java-jacoco.gradle.kts | 2 +- build.gradle.kts | 17 +++++++++++------ sonar-project.properties | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts b/build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts index eaad13e1..ed6e241b 100644 --- a/build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts +++ b/build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts @@ -19,4 +19,4 @@ tasks.named("jacocoTestReport") { html.required.set(true) csv.required.set(false) } -} \ No newline at end of file +} diff --git a/build.gradle.kts b/build.gradle.kts index f60da0d2..bc900ccc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,13 +20,14 @@ tasks.register("jacocoAggregatedReport") { group = "verification" description = "Generates aggregated Jacoco coverage report for all subprojects" - val jacocoSubprojects = subprojects.filter { subproject -> - subproject.plugins.hasPlugin("jacoco") && - file("${subproject.layout.buildDirectory.get()}/jacoco/test.exec").exists() - } + // Configuration Phase: jacoco 플러그인이 있는 모든 서브프로젝트 선택 + // (파일 존재 여부는 체크하지 않음) + val jacocoSubprojects = subprojects.filter { it.plugins.hasPlugin("jacoco") } - dependsOn(jacocoSubprojects.map { it.tasks.named("test") }) + // 모든 test 태스크에 의존 + dependsOn(jacocoSubprojects.mapNotNull { it.tasks.findByName("test") }) + // 소스/클래스는 Configuration Phase에서 설정 가능 additionalSourceDirs.setFrom( jacocoSubprojects.flatMap { it.the()["main"].allSource.srcDirs } ) @@ -36,8 +37,12 @@ tasks.register("jacocoAggregatedReport") { classDirectories.setFrom( jacocoSubprojects.flatMap { it.the()["main"].output } ) + + // Execution Phase: 실제 존재하는 .exec 파일만 수집 executionData.setFrom( - jacocoSubprojects.map { file("${it.layout.buildDirectory.get()}/jacoco/test.exec") } + jacocoSubprojects + .map { file("${it.layout.buildDirectory.get()}/jacoco/test.exec") } + .filter { it.exists() } ) reports { diff --git a/sonar-project.properties b/sonar-project.properties index 3d358033..5c068230 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,5 +1,5 @@ # SonarCloud Configuration -sonar.sources=. +sonar.sources=core/domain/src/main/java,core/service/src/main/java,adapter/persistence/src/main/java,adapter/mongo/src/main/java,adapter/infra/src/main/java,app/api/src/main/java sonar.sourceEncoding=UTF-8 # Java source directories sonar.java.source=17 From 61edd0bfd20c57ea11c499561a01ef9505c305cc Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 07:16:44 +0900 Subject: [PATCH 04/13] =?UTF-8?q?Qodana=20=EC=BD=94=EB=93=9C=20=ED=92=88?= =?UTF-8?q?=EC=A7=88=20=EB=B6=84=EC=84=9D=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EB=B0=B0=ED=8F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Qodana 결과 업로드 설정 활성화 - SARIF 파일 업로드 단계 추가 - GitHub Pages를 통한 분석 결과 리포트 배포 구성 --- .github/workflows/qodana_code_quality.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml index 5dd05612..13472957 100644 --- a/.github/workflows/qodana_code_quality.yml +++ b/.github/workflows/qodana_code_quality.yml @@ -36,6 +36,15 @@ jobs: post-pr-comment: true use-annotations: true # Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job - upload-result: false + upload-result: true # quick-fixes available in Ultimate and Ultimate Plus plans push-fixes: 'none' + - uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ${{ runner.temp }}/qodana/results/report + destination_dir: ./ From bb526b5b0aff620cce0e4a71967427c96478cf8f Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 07:18:52 +0900 Subject: [PATCH 05/13] =?UTF-8?q?Qodana=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=ED=8A=B8=EB=A6=AC=EA=B1=B0=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pull_request 트리거의 브랜치 제한 제거 - push 트리거의 branches-ignore 조건을 branches로 변경하여 main 브랜치 한정 처리 --- .github/workflows/qodana_code_quality.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml index 13472957..b7dd31f1 100644 --- a/.github/workflows/qodana_code_quality.yml +++ b/.github/workflows/qodana_code_quality.yml @@ -7,10 +7,8 @@ name: Qodana on: workflow_dispatch: pull_request: - branches: - - main push: - branches-ignore: + branches: - main jobs: From 0624dbbbd29190fbea37a9521d5e5758ce6bb6fa Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 15:37:09 +0900 Subject: [PATCH 06/13] =?UTF-8?q?GitHub=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EB=B0=8F=20=EB=A6=AC=EB=B7=B0=EC=96=B4=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=ED=95=A0=EB=8B=B9=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20=EC=95=A1=EC=85=98=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - auto-assign 설정 파일 추가 및 PR 리뷰어/Assignee 자동 할당 구성 - Qodana 및 CodeQL SARIF 업로드 액션 버전 업데이트(v2 → v3) - PR 리뷰 단계 워크플로우에 테스트 커버리지 리포트 자동 생성 및 코멘트 추가 - `PR Review Stage` 워크플로우 트리거 및 배포 구성 개선 --- .github/auto-assign.yml | 21 +++++++++ .github/workflows/commit-stage.yml | 4 +- .github/workflows/qodana_code_quality.yml | 3 +- .github/workflows/review-stage.yml | 54 ++++++++++++++++++++--- 4 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 .github/auto-assign.yml diff --git a/.github/auto-assign.yml b/.github/auto-assign.yml new file mode 100644 index 00000000..f928ee74 --- /dev/null +++ b/.github/auto-assign.yml @@ -0,0 +1,21 @@ +# Auto Assign Configuration +# https://github.com/kentaro-m/auto-assign-action + +# 리뷰어 자동 할당 +addReviewers: true +reviewers: +# 리뷰어 목록 (GitHub username) +# - reviewer1 +# - reviewer2 + +# 랜덤으로 선택할 리뷰어 수 (0 = 모두 할당) +numberOfReviewers: 0 + +# PR 작성자를 리뷰어에서 제외 +skipKeywords: + - wip + - WIP + - draft + +# Assignee 자동 할당 +addAssignees: author diff --git a/.github/workflows/commit-stage.yml b/.github/workflows/commit-stage.yml index 81966947..407d6427 100644 --- a/.github/workflows/commit-stage.yml +++ b/.github/workflows/commit-stage.yml @@ -61,7 +61,7 @@ jobs: - name: Upload vulnerability report if: github.ref_name != 'main' && failure() - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: ${{ steps.scan_code.outputs.sarif }} @@ -113,7 +113,7 @@ jobs: severity-cutoff: high - name: Upload vulnerability report - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 if: failure() with: sarif_file: ${{ steps.scan_image.outputs.sarif }} diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml index b7dd31f1..db23e89a 100644 --- a/.github/workflows/qodana_code_quality.yml +++ b/.github/workflows/qodana_code_quality.yml @@ -18,6 +18,7 @@ jobs: contents: write pull-requests: write checks: write + security-events: write steps: - uses: actions/checkout@v4 with: @@ -37,7 +38,7 @@ jobs: upload-result: true # quick-fixes available in Ultimate and Ultimate Plus plans push-fixes: 'none' - - uses: github/codeql-action/upload-sarif@v2 + - uses: github/codeql-action/upload-sarif@v3 with: sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json - name: Deploy to GitHub Pages diff --git a/.github/workflows/review-stage.yml b/.github/workflows/review-stage.yml index 25a840be..103a79b7 100644 --- a/.github/workflows/review-stage.yml +++ b/.github/workflows/review-stage.yml @@ -1,14 +1,54 @@ -name: review-stage.yml +name: PR Review Stage + on: pull_request: branches: - main + types: [ opened, synchronize, reopened ] jobs: - publish: - name: Review Source Code - if: false - runs-on: ubuntu-latest # 배포 환경에 맞게 수정해주어야 함 + # Jacoco 커버리지 리포트를 PR 코멘트로 추가 + coverage-report: + name: Coverage Report + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + cache: gradle + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build and Generate Coverage Report + run: ./gradlew clean build jacocoAggregatedReport + + - name: Add Coverage PR Comment + uses: madrapps/jacoco-report@v1.7.1 + with: + paths: build/reports/jacoco/aggregated/jacocoTestReport.xml + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: 40 + min-coverage-changed-files: 60 + title: "## 📊 테스트 커버리지 리포트" + update-comment: true + + # 자동 리뷰어 할당 + auto-assign: + name: Auto Assign Reviewers + runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - - name: Placeholder for future - run: echo " Review Source Code 워크플로 추후 작성 예정" \ No newline at end of file + - name: Auto Assign Reviewers + uses: kentaro-m/auto-assign-action@v2.0.0 + with: + configuration-path: '.github/auto-assign.yml' From 74dfab2044dacc657054583d8f006458d79b6809 Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 16:22:12 +0900 Subject: [PATCH 07/13] =?UTF-8?q?GitHub=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=ED=86=B5=ED=95=A9,=20=EC=8A=AC=EB=9E=99?= =?UTF-8?q?=20=EC=9B=B9=ED=9B=85=20=EC=B6=94=EA=B0=80,=20=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=EB=93=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B3=91?= =?UTF-8?q?=EB=A0=AC=20=EC=8B=A4=ED=96=89=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/_build.yml | 156 ++++++++++++++++++ .github/workflows/ci.yml | 73 ++++++++ .github/workflows/commit-stage.yml | 132 --------------- .github/workflows/pr-pipeline.yml | 140 ++++++++++++++++ .github/workflows/qodana_code_quality.yml | 49 ------ .github/workflows/release-stage.yml | 17 -- .github/workflows/release.yml | 147 +++++++++++++++++ .github/workflows/review-stage.yml | 54 ------ .github/workflows/sonarcloud-analyze.yml | 49 ------ .../main/kotlin/lm.java-library.gradle.kts | 8 + 10 files changed, 524 insertions(+), 301 deletions(-) create mode 100644 .github/workflows/_build.yml create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/commit-stage.yml create mode 100644 .github/workflows/pr-pipeline.yml delete mode 100644 .github/workflows/qodana_code_quality.yml delete mode 100644 .github/workflows/release-stage.yml create mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/review-stage.yml delete mode 100644 .github/workflows/sonarcloud-analyze.yml diff --git a/.github/workflows/_build.yml b/.github/workflows/_build.yml new file mode 100644 index 00000000..a52ae016 --- /dev/null +++ b/.github/workflows/_build.yml @@ -0,0 +1,156 @@ +# Reusable Build Workflow +# 다른 워크플로우에서 workflow_call로 호출하여 재사용 + +name: Reusable Build + +on: + # workflow_call: 다른 워크플로우에서 "uses: ./.github/workflows/_build.yml"로 호출 가능하게 함 + workflow_call: + # inputs: 호출하는 워크플로우에서 전달할 수 있는 매개변수 정의 + inputs: + java-version: + description: 'Java version to use' + required: false + default: '17' + type: string + run-tests: + description: 'Run tests' + required: false + default: true + type: boolean + generate-coverage: + description: 'Generate Jacoco coverage report' + required: false + default: true + type: boolean + publish-build-scan: + description: 'Publish Gradle Build Scan' + required: false + default: true + type: boolean + # secrets: 호출하는 워크플로우에서 전달할 시크릿 정의 + secrets: + SLACK_WEBHOOK_URL: + description: 'Slack Incoming Webhook URL for notifications' + required: false + # outputs: 이 워크플로우의 결과를 호출한 워크플로우에 반환 + outputs: + build-outcome: + description: 'Build outcome (success/failure)' + # jobs.build.outputs.outcome 값을 외부로 노출 + value: ${{ jobs.build.outputs.outcome }} + build-scan-url: + description: 'Gradle Build Scan URL' + value: ${{ jobs.build.outputs.build-scan-url }} + +jobs: + build: + name: Build & Test + runs-on: ubuntu-latest + # outputs: 이 job의 결과를 다른 job이나 워크플로우에서 참조 가능하게 함 + outputs: + outcome: ${{ steps.build.outcome }} + build-scan-url: ${{ steps.gradle-build.outputs.build-scan-url }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # fetch-depth: 0 = 전체 히스토리 가져옴 (SonarCloud 분석에 필요) + fetch-depth: 0 + + - name: Set up JDK ${{ inputs.java-version }} + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ inputs.java-version }} + # cache: gradle = ~/.gradle/caches 디렉토리를 자동으로 캐싱하여 빌드 속도 향상 + cache: gradle + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + # build-scan-publish: true = Gradle Build Scan을 scans.gradle.com에 발행 + # Build Scan은 빌드 성능, 의존성, 테스트 결과 등 상세 정보를 웹에서 확인 가능 + build-scan-publish: ${{ inputs.publish-build-scan }} + build-scan-terms-of-use-url: "https://gradle.com/help/legal-terms-of-use" + build-scan-terms-of-use-agree: "yes" + + - name: Grant execute permission + run: chmod +x gradlew + + - name: Build + # id: gradle-build = Build Scan URL을 outputs로 가져오기 위해 필요 + id: gradle-build + # generate-coverage가 true면 'jacocoAggregatedReport' 추가, false면 빈 문자열 + run: ./gradlew clean build --parallel ${{ inputs.generate-coverage && 'jacocoAggregatedReport' || '' }} + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-artifacts + # path: | = 여러 경로를 멀티라인으로 지정 + # **/build/classes/ = 모든 하위 디렉토리의 build/classes 폴더 + path: | + **/build/classes/ + **/build/resources/ + **/build/libs/ + # retention-days: 1 = 아티팩트 보관 기간 (1일 후 자동 삭제) + retention-days: 1 + + - name: Upload Jacoco report + # if: 조건부 실행 - generate-coverage가 true일 때만 실행 + if: inputs.generate-coverage + uses: actions/upload-artifact@v4 + with: + name: jacoco-report + path: | + build/reports/jacoco/aggregated/ + **/build/reports/jacoco/test/ + retention-days: 14 + + - name: Upload test results + # always(): 이전 step이 실패해도 항상 실행 (테스트 실패 시에도 결과 업로드) + # inputs.run-tests && always(): 테스트 실행했을 때만 + 항상 실행 + if: inputs.run-tests && always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: '**/build/test-results/' + retention-days: 7 + + # Slack 알림 (빌드 실패 시 또는 Build Scan URL 공유) + - name: Notify Slack on failure + # secrets.SLACK_WEBHOOK_URL이 설정되어 있고 빌드 실패 시에만 실행 + if: failure() && secrets.SLACK_WEBHOOK_URL != '' + uses: slackapi/slack-github-action@v2.0.0 + with: + # webhook: Slack Incoming Webhook URL + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + # payload: Slack 메시지 JSON 형식 + payload: | + { + "text": "빌드 실패", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*빌드 실패*\n*Repository:* ${{ github.repository }}\n*Branch:* ${{ github.ref_name }}\n*Commit:* `${{ github.sha }}`" + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Build Scan:*\n${{ steps.gradle-build.outputs.build-scan-url || 'N/A' }}" + }, + { + "type": "mrkdwn", + "text": "*Workflow:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>" + } + ] + } + ] + } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..620cb4a8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,73 @@ +# CI Pipeline +# 브랜치 푸시 시 빠른 검증 (빌드, 테스트, 보안 스캔) +# 목표: 5분 이내 피드백 +name: CI + +on: + push: + branches: + - develop + - main + # 태그는 release.yml에서 처리 + tags-ignore: + - '**' + +jobs: + # 1. 빌드 및 테스트 + build: + name: Build & Test + uses: ./.github/workflows/_build.yml + with: + java-version: '17' + run-tests: true + generate-coverage: true + publish-build-scan: true + secrets: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + # 2. 의존성 그래프 및 보안 스캔 + security-scan: + name: Security Scan + needs: [ build ] + runs-on: ubuntu-latest + permissions: + contents: write + security-events: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + cache: gradle + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + # GitHub Dependency Graph에 의존성 정보 제출 + # Security > Dependabot alerts에서 취약점 확인 가능 + - name: Submit Dependency Graph + uses: gradle/actions/dependency-submission@v4 + with: + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/help/legal-terms-of-use" + build-scan-terms-of-use-agree: "yes" + + # 소스 코드 취약점 스캔 (develop 브랜치에서만) + - name: Code Vulnerability Scan + if: github.ref_name == 'develop' + uses: anchore/scan-action@v3 + id: scan + with: + path: "${{ github.workspace }}" + fail-build: false + severity-cutoff: high + + - name: Upload Vulnerability Report + if: github.ref_name == 'develop' && failure() + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ${{ steps.scan.outputs.sarif }} diff --git a/.github/workflows/commit-stage.yml b/.github/workflows/commit-stage.yml deleted file mode 100644 index 407d6427..00000000 --- a/.github/workflows/commit-stage.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: commit-stage.yml -on: - push: - branches: - - develop - - main - tags: - - 'v*.*.*' # 'v'로 시작하는 시맨틱 버전 태그 푸시 시 워크플로 실행 - -env: - REGISTRY: ghcr.io - IMAGE_NAME: chan99k/learning-manager - -jobs: - build: - name: Checkout, Build and Code Scan - runs-on: ubuntu-latest # 배포 환경에 맞게 수정해주어야 함 - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - name: SetUp Java - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: 17 - cache: gradle - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Build Project with Gradle - run: ./gradlew clean build - - - name: Generate Jacoco Coverage Report - run: ./gradlew jacocoAggregatedReport - - - name: Upload Jacoco Coverage Report - uses: actions/upload-artifact@v4 - with: - name: jacoco-report - path: | - build/reports/jacoco/aggregated/ - **/build/reports/jacoco/test/ - retention-days: 14 - - - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@v4 - with: - build-scan-publish: true - build-scan-terms-of-use-url: "https://gradle.com/help/legal-terms-of-use" - build-scan-terms-of-use-agree: "yes" - - - name: Code vulnerability scanning - if: github.ref_name != 'main' - uses: anchore/scan-action@v3 - id: scan_code - with: - path: "${{ github.workspace }}" - fail-build: false # 정책에 따라 변경을 고려하여야 할 듯 - severity-cutoff: high - - - name: Upload vulnerability report - if: github.ref_name != 'main' && failure() - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: ${{ steps.scan_code.outputs.sarif }} - - package: - name: Package and Publish - if: startsWith(github.ref, 'refs/tags/v') # 'v'로 시작하는 태그가 푸시되었을 때만 실행 - needs: [ build ] - runs-on: ubuntu-latest # 배포 환경에 맞게 수정해주어야 함 - permissions: - contents: read - packages: write - security-events: write - steps: - - name: Checkout source code - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} - - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: 17 - cache: gradle - - - name: Define Image Tags from Git Tag - id: image_tags - run: | - RAW_TAG="${{ github.ref_name }}" - VERSION=${RAW_TAG#v} - echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - echo "IMAGE_NAME_VERSIONED=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$VERSION" >> $GITHUB_OUTPUT - echo "IMAGE_NAME_LATEST=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_OUTPUT - echo "Raw tag was: $RAW_TAG, Version used: $VERSION" - - - - name: Build container image - run: | - chmod +x gradlew - ./gradlew bootBuildImage \ - --imageName ${{ steps.image_tags.outputs.IMAGE_NAME_VERSIONED }} - - - name: OCI image vulnerability scanning - uses: anchore/scan-action@v3 - id: scan_image - with: - image: "${{ steps.image_tags.outputs.IMAGE_NAME_VERSIONED }}" - fail-build: true - severity-cutoff: high - - - name: Upload vulnerability report - uses: github/codeql-action/upload-sarif@v3 - if: failure() - with: - sarif_file: ${{ steps.scan_image.outputs.sarif }} - - - name: Log into container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # 시맨틱 버전 태그로 푸시 - - name: Publish container image - run: | - docker push ${{ steps.image_tags.outputs.IMAGE_NAME_VERSIONED }} - diff --git a/.github/workflows/pr-pipeline.yml b/.github/workflows/pr-pipeline.yml new file mode 100644 index 00000000..ab03e6cf --- /dev/null +++ b/.github/workflows/pr-pipeline.yml @@ -0,0 +1,140 @@ +# PR Pipeline +# PR 생성/업데이트 시 코드 품질 검증 및 리뷰 지원 +name: PR Pipeline + +on: + pull_request: + branches: + - main + types: [ opened, synchronize, reopened ] + +jobs: + # 1. 빌드 및 테스트 + build: + name: Build & Test + uses: ./.github/workflows/_build.yml + with: + java-version: '17' + run-tests: true + generate-coverage: true + publish-build-scan: true + secrets: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + # 2. 커버리지 리포트 PR 코멘트 + coverage-report: + name: Coverage Report + needs: [ build ] + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - name: Download Jacoco report + uses: actions/download-artifact@v4 + with: + name: jacoco-report + path: . + + - name: Add Coverage PR Comment + uses: madrapps/jacoco-report@v1.7.1 + with: + paths: build/reports/jacoco/aggregated/jacocoTestReport.xml + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: 40 + min-coverage-changed-files: 60 + title: "## 테스트 커버리지 리포트" + update-comment: true + + # 3. SonarCloud 정적 분석 + sonarcloud: + name: SonarCloud + needs: [ build ] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-artifacts + path: . + + - name: Download Jacoco report + uses: actions/download-artifact@v4 + with: + name: jacoco-report + path: . + + - name: Set SonarCloud Project Key + run: | + REPO_NAME=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 2) + ORG_NAME=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 1) + echo "SONAR_PROJECT_KEY=${ORG_NAME}_${REPO_NAME}" >> $GITHUB_ENV + + - name: Analyze with SonarCloud + uses: SonarSource/sonarcloud-github-action@master + continue-on-error: true + env: + GITHUB_TOKEN: ${{ secrets.SECRET_GITHUB_BOT }} + SONAR_TOKEN: ${{ secrets.SECRET_SONARQUBE }} + with: + args: | + -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} + -Dsonar.organization=f-lab-edu-1 + + # 4. Qodana 정적 분석 (JetBrains) + qodana: + name: Qodana + needs: [ build ] + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + security-events: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + + - name: Qodana Scan + uses: JetBrains/qodana-action@v2025.2 + continue-on-error: true + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} + with: + # PR 모드: 변경된 파일만 분석하여 속도 향상 + pr-mode: true + use-caches: true + post-pr-comment: true + use-annotations: true + upload-result: true + push-fixes: 'none' + + - name: Upload SARIF to GitHub Security + if: always() + uses: github/codeql-action/upload-sarif@v3 + continue-on-error: true + with: + sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json + + # 5. 자동 리뷰어 할당 + auto-assign: + name: Auto Assign + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auto Assign Reviewers + uses: kentaro-m/auto-assign-action@v2.0.0 + with: + configuration-path: '.github/auto-assign.yml' diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml deleted file mode 100644 index db23e89a..00000000 --- a/.github/workflows/qodana_code_quality.yml +++ /dev/null @@ -1,49 +0,0 @@ -#-------------------------------------------------------------------------------# -# Discover all capabilities of Qodana in our documentation # -# https://www.jetbrains.com/help/qodana/about-qodana.html # -#-------------------------------------------------------------------------------# - -name: Qodana -on: - workflow_dispatch: - pull_request: - push: - branches: - - main - -jobs: - qodana: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - checks: write - security-events: write - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - name: 'Qodana Scan' - uses: JetBrains/qodana-action@v2025.2 - env: - QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} - with: - # When pr-mode is set to true, Qodana analyzes only the files that have been changed - pr-mode: false - use-caches: true - post-pr-comment: true - use-annotations: true - # Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job - upload-result: true - # quick-fixes available in Ultimate and Ultimate Plus plans - push-fixes: 'none' - - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ${{ runner.temp }}/qodana/results/report - destination_dir: ./ diff --git a/.github/workflows/release-stage.yml b/.github/workflows/release-stage.yml deleted file mode 100644 index a6a265e2..00000000 --- a/.github/workflows/release-stage.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: release-stage.yml -on: - push: - branches: - - main - tags: - - 'v*.*.*' # 'v'로 시작하는 시맨틱 버전 태그 푸시 시 워크플로 실행 - -jobs: - publish: - name: Publish release version - if : false - runs-on: ubuntu-latest # 배포 환경에 맞게 수정해주어야 함 - steps: - - name: Placeholder for future - run: echo "Publish release 워크플로 추후 작성 예정" - diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..13345575 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,147 @@ +# Release Pipeline +# 태그 푸시 시 Docker 이미지 빌드 및 레지스트리 배포 +name: Release + +on: + push: + tags: + - 'v*.*.*' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: chan99k/learning-manager + +jobs: + # 1. 빌드 및 테스트 (릴리스 전 최종 검증) + build: + name: Build & Test + uses: ./.github/workflows/_build.yml + with: + java-version: '17' + run-tests: true + generate-coverage: false + publish-build-scan: true + secrets: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + # 2. Docker 이미지 빌드 및 배포 + docker: + name: Docker Build & Push + needs: [ build ] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + security-events: write + outputs: + image-tag: ${{ steps.meta.outputs.version }} + image-name: ${{ steps.meta.outputs.tags }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + cache: gradle + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + # 태그에서 버전 추출 (v1.0.0 -> 1.0.0) + - name: Extract version from tag + id: meta + run: | + VERSION=${GITHUB_REF_NAME#v} + IMAGE_FULL="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}" + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "tags=${IMAGE_FULL}" >> $GITHUB_OUTPUT + + # Spring Boot의 Buildpacks로 OCI 이미지 빌드 -> 추후 dockerfile 최적화로 변경 필요 + - name: Build container image + run: | + chmod +x gradlew + ./gradlew bootBuildImage --imageName ${{ steps.meta.outputs.tags }} + + # 컨테이너 이미지 취약점 스캔 + - name: Scan container image + uses: anchore/scan-action@v3 + id: scan + with: + image: ${{ steps.meta.outputs.tags }} + fail-build: true + severity-cutoff: high + + - name: Upload scan report + if: failure() + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ${{ steps.scan.outputs.sarif }} + + # GitHub Container Registry 로그인 + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # 이미지 푸시 + - name: Push container image + run: docker push ${{ steps.meta.outputs.tags }} + + # 3. GitHub Release 생성 (선택적) + create-release: + name: Create GitHub Release + needs: [ docker ] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + + # 4. Slack 릴리스 알림 + notify: + name: Notify Release + needs: [ docker ] + if: always() && secrets.SLACK_WEBHOOK_URL != '' + runs-on: ubuntu-latest + steps: + - name: Send Slack notification + uses: slackapi/slack-github-action@v2.0.0 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ needs.docker.result == 'success' && 'Release 성공' || 'Release 실패' }}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "${{ needs.docker.result == 'success' && '*Release 성공*' || '*Release 실패*' }}\n*Version:* `${{ github.ref_name }}`\n*Image:* `${{ needs.docker.outputs.image-name }}`" + } + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { "type": "plain_text", "text": "View Release" }, + "url": "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}" + } + ] + } + ] + } diff --git a/.github/workflows/review-stage.yml b/.github/workflows/review-stage.yml deleted file mode 100644 index 103a79b7..00000000 --- a/.github/workflows/review-stage.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: PR Review Stage - -on: - pull_request: - branches: - - main - types: [ opened, synchronize, reopened ] - -jobs: - # Jacoco 커버리지 리포트를 PR 코멘트로 추가 - coverage-report: - name: Coverage Report - runs-on: ubuntu-latest - permissions: - pull-requests: write - contents: read - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: 17 - cache: gradle - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Build and Generate Coverage Report - run: ./gradlew clean build jacocoAggregatedReport - - - name: Add Coverage PR Comment - uses: madrapps/jacoco-report@v1.7.1 - with: - paths: build/reports/jacoco/aggregated/jacocoTestReport.xml - token: ${{ secrets.GITHUB_TOKEN }} - min-coverage-overall: 40 - min-coverage-changed-files: 60 - title: "## 📊 테스트 커버리지 리포트" - update-comment: true - - # 자동 리뷰어 할당 - auto-assign: - name: Auto Assign Reviewers - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Auto Assign Reviewers - uses: kentaro-m/auto-assign-action@v2.0.0 - with: - configuration-path: '.github/auto-assign.yml' diff --git a/.github/workflows/sonarcloud-analyze.yml b/.github/workflows/sonarcloud-analyze.yml deleted file mode 100644 index 18b66f4f..00000000 --- a/.github/workflows/sonarcloud-analyze.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: F-Lab SonarCloud Code Analyze - -on: - pull_request: - types: [opened, synchronize, reopened] - workflow_dispatch: - -jobs: - CodeAnalyze: - runs-on: ubuntu-latest - - steps: - - 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: gradle - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Build and Generate Jacoco Report - run: ./gradlew clean build jacocoAggregatedReport - - - name: Set SonarCloud Project Key - run: | - REPO_NAME=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 2) - ORG_NAME=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 1) - SONAR_PROJECT_KEY="${ORG_NAME}_${REPO_NAME}" - echo "SONAR_PROJECT_KEY=$SONAR_PROJECT_KEY" >> $GITHUB_ENV - - - name: Analyze with SonarCloud - uses: SonarSource/sonarcloud-github-action@master - id: analyze-sonarcloud - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.SECRET_GITHUB_BOT }} - SONAR_TOKEN: ${{ secrets.SECRET_SONARQUBE }} - with: - args: - -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} - -Dsonar.organization=f-lab-edu-1 - - \ No newline at end of file diff --git a/build-logic/src/main/kotlin/lm.java-library.gradle.kts b/build-logic/src/main/kotlin/lm.java-library.gradle.kts index 8fca4a67..cb4b919f 100644 --- a/build-logic/src/main/kotlin/lm.java-library.gradle.kts +++ b/build-logic/src/main/kotlin/lm.java-library.gradle.kts @@ -18,6 +18,14 @@ tasks.withType().configureEach { tasks.withType().configureEach { useJUnitPlatform() + + // maxParallelForks: 동시에 실행할 테스트 프로세스 수 + // Runtime.getRuntime().availableProcessors() / 2 -> CPU 코어의 절반 사용 + maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) + + // forkEvery: N개 테스트마다 새 JVM 프로세스 생성 (메모리 누수 방지) + // 0 = 재시작 안 함 (기본값), 250 = 250개 테스트마다 재시작 + setForkEvery(250) } dependencyManagement { From b71c3a859e2440c1d78521ebd3b912e3bbbf1bd4 Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 16:44:21 +0900 Subject: [PATCH 08/13] =?UTF-8?q?GitHub=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=ED=86=B5=ED=95=A9,=20=EC=8A=AC=EB=9E=99?= =?UTF-8?q?=20=EC=9B=B9=ED=9B=85=20=EC=B6=94=EA=B0=80,=20=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=EB=93=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B3=91?= =?UTF-8?q?=EB=A0=AC=20=EC=8B=A4=ED=96=89=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/_build.yml | 20 +++++++++++--------- .github/workflows/release.yml | 25 +++++++++++++++++++++---- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/.github/workflows/_build.yml b/.github/workflows/_build.yml index a52ae016..4776cf01 100644 --- a/.github/workflows/_build.yml +++ b/.github/workflows/_build.yml @@ -39,9 +39,9 @@ on: description: 'Build outcome (success/failure)' # jobs.build.outputs.outcome 값을 외부로 노출 value: ${{ jobs.build.outputs.outcome }} - build-scan-url: + build-scan: description: 'Gradle Build Scan URL' - value: ${{ jobs.build.outputs.build-scan-url }} + value: ${{ jobs.build.outputs.build-scan }} jobs: build: @@ -50,7 +50,10 @@ jobs: # outputs: 이 job의 결과를 다른 job이나 워크플로우에서 참조 가능하게 함 outputs: outcome: ${{ steps.build.outcome }} - build-scan-url: ${{ steps.gradle-build.outputs.build-scan-url }} + build-scan: ${{ steps.gradle-build.outputs.build-scan }} + # env: secrets를 환경변수로 변환 (if 조건에서 secrets 직접 참조 불가) + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} steps: - name: Checkout uses: actions/checkout@v4 @@ -118,14 +121,13 @@ jobs: path: '**/build/test-results/' retention-days: 7 - # Slack 알림 (빌드 실패 시 또는 Build Scan URL 공유) + # Slack 알림 (빌드 실패 시) - name: Notify Slack on failure - # secrets.SLACK_WEBHOOK_URL이 설정되어 있고 빌드 실패 시에만 실행 - if: failure() && secrets.SLACK_WEBHOOK_URL != '' + # env.SLACK_WEBHOOK_URL 사용 (secrets는 if 조건에서 직접 참조 불가) + if: failure() && env.SLACK_WEBHOOK_URL != '' uses: slackapi/slack-github-action@v2.0.0 with: - # webhook: Slack Incoming Webhook URL - webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook: ${{ env.SLACK_WEBHOOK_URL }} webhook-type: incoming-webhook # payload: Slack 메시지 JSON 형식 payload: | @@ -144,7 +146,7 @@ jobs: "fields": [ { "type": "mrkdwn", - "text": "*Build Scan:*\n${{ steps.gradle-build.outputs.build-scan-url || 'N/A' }}" + "text": "*Build Scan:*\n${{ steps.gradle-build.outputs.build-scan || 'N/A' }}" }, { "type": "mrkdwn", diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 13345575..0f83dd49 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -114,23 +114,40 @@ jobs: notify: name: Notify Release needs: [ docker ] - if: always() && secrets.SLACK_WEBHOOK_URL != '' + if: always() runs-on: ubuntu-latest + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} steps: + # 릴리스 결과에 따라 메시지 설정 + - name: Set notification message + id: message + env: + DOCKER_RESULT: ${{ needs.docker.result }} + run: | + if [ "$DOCKER_RESULT" == "success" ]; then + echo "status=Release 성공" >> $GITHUB_OUTPUT + echo "status_bold=*Release 성공*" >> $GITHUB_OUTPUT + else + echo "status=Release 실패" >> $GITHUB_OUTPUT + echo "status_bold=*Release 실패*" >> $GITHUB_OUTPUT + fi + - name: Send Slack notification + if: env.SLACK_WEBHOOK_URL != '' uses: slackapi/slack-github-action@v2.0.0 with: - webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook: ${{ env.SLACK_WEBHOOK_URL }} webhook-type: incoming-webhook payload: | { - "text": "${{ needs.docker.result == 'success' && 'Release 성공' || 'Release 실패' }}", + "text": "${{ steps.message.outputs.status }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", - "text": "${{ needs.docker.result == 'success' && '*Release 성공*' || '*Release 실패*' }}\n*Version:* `${{ github.ref_name }}`\n*Image:* `${{ needs.docker.outputs.image-name }}`" + "text": "${{ steps.message.outputs.status_bold }}\n*Version:* `${{ github.ref_name }}`\n*Image:* `${{ needs.docker.outputs.image-name }}`" } }, { From 65d4cff20cf5b2881b081da460555e6ab2a7fe76 Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 06:58:10 +0900 Subject: [PATCH 09/13] =?UTF-8?q?Jacoco=20ci=20=ED=94=8C=EB=A1=9C=EC=9A=B0?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 프로젝트 전반에 Jacoco 플러그인 적용 및 코드 커버리지 설정 추가 - SonarCloud 분석을 위한 설정 파일 작성 - CI 워크플로우에 Jacoco 리포트 생성 및 업로드 단계 추가 - 라이브러리 버전 관리 파일에 Jacoco 버전 명시 - 테스트 실행 후 코드 커버리지 리포트 생성 자동화 --- .github/workflows/commit-stage.yml | 132 ++++++++++++++++++ .../src/main/kotlin/lm.java-jacoco.gradle.kts | 2 +- build.gradle.kts | 17 +-- sonar-project.properties | 2 +- 4 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/commit-stage.yml diff --git a/.github/workflows/commit-stage.yml b/.github/workflows/commit-stage.yml new file mode 100644 index 00000000..81966947 --- /dev/null +++ b/.github/workflows/commit-stage.yml @@ -0,0 +1,132 @@ +name: commit-stage.yml +on: + push: + branches: + - develop + - main + tags: + - 'v*.*.*' # 'v'로 시작하는 시맨틱 버전 태그 푸시 시 워크플로 실행 + +env: + REGISTRY: ghcr.io + IMAGE_NAME: chan99k/learning-manager + +jobs: + build: + name: Checkout, Build and Code Scan + runs-on: ubuntu-latest # 배포 환경에 맞게 수정해주어야 함 + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: SetUp Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + cache: gradle + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build Project with Gradle + run: ./gradlew clean build + + - name: Generate Jacoco Coverage Report + run: ./gradlew jacocoAggregatedReport + + - name: Upload Jacoco Coverage Report + uses: actions/upload-artifact@v4 + with: + name: jacoco-report + path: | + build/reports/jacoco/aggregated/ + **/build/reports/jacoco/test/ + retention-days: 14 + + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@v4 + with: + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/help/legal-terms-of-use" + build-scan-terms-of-use-agree: "yes" + + - name: Code vulnerability scanning + if: github.ref_name != 'main' + uses: anchore/scan-action@v3 + id: scan_code + with: + path: "${{ github.workspace }}" + fail-build: false # 정책에 따라 변경을 고려하여야 할 듯 + severity-cutoff: high + + - name: Upload vulnerability report + if: github.ref_name != 'main' && failure() + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: ${{ steps.scan_code.outputs.sarif }} + + package: + name: Package and Publish + if: startsWith(github.ref, 'refs/tags/v') # 'v'로 시작하는 태그가 푸시되었을 때만 실행 + needs: [ build ] + runs-on: ubuntu-latest # 배포 환경에 맞게 수정해주어야 함 + permissions: + contents: read + packages: write + security-events: write + steps: + - name: Checkout source code + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + cache: gradle + + - name: Define Image Tags from Git Tag + id: image_tags + run: | + RAW_TAG="${{ github.ref_name }}" + VERSION=${RAW_TAG#v} + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + echo "IMAGE_NAME_VERSIONED=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$VERSION" >> $GITHUB_OUTPUT + echo "IMAGE_NAME_LATEST=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_OUTPUT + echo "Raw tag was: $RAW_TAG, Version used: $VERSION" + + + - name: Build container image + run: | + chmod +x gradlew + ./gradlew bootBuildImage \ + --imageName ${{ steps.image_tags.outputs.IMAGE_NAME_VERSIONED }} + + - name: OCI image vulnerability scanning + uses: anchore/scan-action@v3 + id: scan_image + with: + image: "${{ steps.image_tags.outputs.IMAGE_NAME_VERSIONED }}" + fail-build: true + severity-cutoff: high + + - name: Upload vulnerability report + uses: github/codeql-action/upload-sarif@v2 + if: failure() + with: + sarif_file: ${{ steps.scan_image.outputs.sarif }} + + - name: Log into container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # 시맨틱 버전 태그로 푸시 + - name: Publish container image + run: | + docker push ${{ steps.image_tags.outputs.IMAGE_NAME_VERSIONED }} + diff --git a/build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts b/build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts index ed6e241b..eaad13e1 100644 --- a/build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts +++ b/build-logic/src/main/kotlin/lm.java-jacoco.gradle.kts @@ -19,4 +19,4 @@ tasks.named("jacocoTestReport") { html.required.set(true) csv.required.set(false) } -} +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index bc900ccc..f60da0d2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,14 +20,13 @@ tasks.register("jacocoAggregatedReport") { group = "verification" description = "Generates aggregated Jacoco coverage report for all subprojects" - // Configuration Phase: jacoco 플러그인이 있는 모든 서브프로젝트 선택 - // (파일 존재 여부는 체크하지 않음) - val jacocoSubprojects = subprojects.filter { it.plugins.hasPlugin("jacoco") } + val jacocoSubprojects = subprojects.filter { subproject -> + subproject.plugins.hasPlugin("jacoco") && + file("${subproject.layout.buildDirectory.get()}/jacoco/test.exec").exists() + } - // 모든 test 태스크에 의존 - dependsOn(jacocoSubprojects.mapNotNull { it.tasks.findByName("test") }) + dependsOn(jacocoSubprojects.map { it.tasks.named("test") }) - // 소스/클래스는 Configuration Phase에서 설정 가능 additionalSourceDirs.setFrom( jacocoSubprojects.flatMap { it.the()["main"].allSource.srcDirs } ) @@ -37,12 +36,8 @@ tasks.register("jacocoAggregatedReport") { classDirectories.setFrom( jacocoSubprojects.flatMap { it.the()["main"].output } ) - - // Execution Phase: 실제 존재하는 .exec 파일만 수집 executionData.setFrom( - jacocoSubprojects - .map { file("${it.layout.buildDirectory.get()}/jacoco/test.exec") } - .filter { it.exists() } + jacocoSubprojects.map { file("${it.layout.buildDirectory.get()}/jacoco/test.exec") } ) reports { diff --git a/sonar-project.properties b/sonar-project.properties index 5c068230..3d358033 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,5 +1,5 @@ # SonarCloud Configuration -sonar.sources=core/domain/src/main/java,core/service/src/main/java,adapter/persistence/src/main/java,adapter/mongo/src/main/java,adapter/infra/src/main/java,app/api/src/main/java +sonar.sources=. sonar.sourceEncoding=UTF-8 # Java source directories sonar.java.source=17 From 7b2814b3fe84ec33f9ee38af5c4a0f03cf7a086a Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 06:58:40 +0900 Subject: [PATCH 10/13] =?UTF-8?q?Qodana=20=EC=BD=94=EB=93=9C=20=ED=92=88?= =?UTF-8?q?=EC=A7=88=20=EB=B6=84=EC=84=9D=20=EB=B0=8F=20Gradle=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Qodana 코드 품질 분석 도구 CI 워크플로우 추가 - qodana.yaml 파일 생성 및 프로젝트 JDK, 린터 설정 - SonarCloud 워크플로우에 JDK 17 및 Gradle 설정 추가 - 코드 커버리지 리포트 생성을 위한 Gradle 명령어 구성 - actions/checkout 및 관련 액션 버전 업데이트 --- .github/workflows/qodana_code_quality.yml | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/qodana_code_quality.yml diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml new file mode 100644 index 00000000..5dd05612 --- /dev/null +++ b/.github/workflows/qodana_code_quality.yml @@ -0,0 +1,41 @@ +#-------------------------------------------------------------------------------# +# Discover all capabilities of Qodana in our documentation # +# https://www.jetbrains.com/help/qodana/about-qodana.html # +#-------------------------------------------------------------------------------# + +name: Qodana +on: + workflow_dispatch: + pull_request: + branches: + - main + push: + branches-ignore: + - main + +jobs: + qodana: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2025.2 + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} + with: + # When pr-mode is set to true, Qodana analyzes only the files that have been changed + pr-mode: false + use-caches: true + post-pr-comment: true + use-annotations: true + # Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job + upload-result: false + # quick-fixes available in Ultimate and Ultimate Plus plans + push-fixes: 'none' From d31438e810ccab7f481c9bfdec74ac4c942136a3 Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 15:37:09 +0900 Subject: [PATCH 11/13] =?UTF-8?q?GitHub=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EB=B0=8F=20=EB=A6=AC=EB=B7=B0=EC=96=B4=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=ED=95=A0=EB=8B=B9=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20=EC=95=A1=EC=85=98=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - auto-assign 설정 파일 추가 및 PR 리뷰어/Assignee 자동 할당 구성 - Qodana 및 CodeQL SARIF 업로드 액션 버전 업데이트(v2 → v3) - PR 리뷰 단계 워크플로우에 테스트 커버리지 리포트 자동 생성 및 코멘트 추가 - `PR Review Stage` 워크플로우 트리거 및 배포 구성 개선 --- .github/workflows/commit-stage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/commit-stage.yml b/.github/workflows/commit-stage.yml index 81966947..407d6427 100644 --- a/.github/workflows/commit-stage.yml +++ b/.github/workflows/commit-stage.yml @@ -61,7 +61,7 @@ jobs: - name: Upload vulnerability report if: github.ref_name != 'main' && failure() - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: ${{ steps.scan_code.outputs.sarif }} @@ -113,7 +113,7 @@ jobs: severity-cutoff: high - name: Upload vulnerability report - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 if: failure() with: sarif_file: ${{ steps.scan_image.outputs.sarif }} From 59880138a29c5fea295e6c66cf562ddec1ec52e2 Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 16:22:12 +0900 Subject: [PATCH 12/13] =?UTF-8?q?GitHub=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=ED=86=B5=ED=95=A9,=20=EC=8A=AC=EB=9E=99?= =?UTF-8?q?=20=EC=9B=B9=ED=9B=85=20=EC=B6=94=EA=B0=80,=20=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=EB=93=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B3=91?= =?UTF-8?q?=EB=A0=AC=20=EC=8B=A4=ED=96=89=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/commit-stage.yml | 132 ---------------------- .github/workflows/qodana_code_quality.yml | 41 ------- 2 files changed, 173 deletions(-) delete mode 100644 .github/workflows/commit-stage.yml delete mode 100644 .github/workflows/qodana_code_quality.yml diff --git a/.github/workflows/commit-stage.yml b/.github/workflows/commit-stage.yml deleted file mode 100644 index 407d6427..00000000 --- a/.github/workflows/commit-stage.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: commit-stage.yml -on: - push: - branches: - - develop - - main - tags: - - 'v*.*.*' # 'v'로 시작하는 시맨틱 버전 태그 푸시 시 워크플로 실행 - -env: - REGISTRY: ghcr.io - IMAGE_NAME: chan99k/learning-manager - -jobs: - build: - name: Checkout, Build and Code Scan - runs-on: ubuntu-latest # 배포 환경에 맞게 수정해주어야 함 - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - name: SetUp Java - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: 17 - cache: gradle - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Build Project with Gradle - run: ./gradlew clean build - - - name: Generate Jacoco Coverage Report - run: ./gradlew jacocoAggregatedReport - - - name: Upload Jacoco Coverage Report - uses: actions/upload-artifact@v4 - with: - name: jacoco-report - path: | - build/reports/jacoco/aggregated/ - **/build/reports/jacoco/test/ - retention-days: 14 - - - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@v4 - with: - build-scan-publish: true - build-scan-terms-of-use-url: "https://gradle.com/help/legal-terms-of-use" - build-scan-terms-of-use-agree: "yes" - - - name: Code vulnerability scanning - if: github.ref_name != 'main' - uses: anchore/scan-action@v3 - id: scan_code - with: - path: "${{ github.workspace }}" - fail-build: false # 정책에 따라 변경을 고려하여야 할 듯 - severity-cutoff: high - - - name: Upload vulnerability report - if: github.ref_name != 'main' && failure() - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: ${{ steps.scan_code.outputs.sarif }} - - package: - name: Package and Publish - if: startsWith(github.ref, 'refs/tags/v') # 'v'로 시작하는 태그가 푸시되었을 때만 실행 - needs: [ build ] - runs-on: ubuntu-latest # 배포 환경에 맞게 수정해주어야 함 - permissions: - contents: read - packages: write - security-events: write - steps: - - name: Checkout source code - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} - - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: 17 - cache: gradle - - - name: Define Image Tags from Git Tag - id: image_tags - run: | - RAW_TAG="${{ github.ref_name }}" - VERSION=${RAW_TAG#v} - echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - echo "IMAGE_NAME_VERSIONED=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$VERSION" >> $GITHUB_OUTPUT - echo "IMAGE_NAME_LATEST=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_OUTPUT - echo "Raw tag was: $RAW_TAG, Version used: $VERSION" - - - - name: Build container image - run: | - chmod +x gradlew - ./gradlew bootBuildImage \ - --imageName ${{ steps.image_tags.outputs.IMAGE_NAME_VERSIONED }} - - - name: OCI image vulnerability scanning - uses: anchore/scan-action@v3 - id: scan_image - with: - image: "${{ steps.image_tags.outputs.IMAGE_NAME_VERSIONED }}" - fail-build: true - severity-cutoff: high - - - name: Upload vulnerability report - uses: github/codeql-action/upload-sarif@v3 - if: failure() - with: - sarif_file: ${{ steps.scan_image.outputs.sarif }} - - - name: Log into container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # 시맨틱 버전 태그로 푸시 - - name: Publish container image - run: | - docker push ${{ steps.image_tags.outputs.IMAGE_NAME_VERSIONED }} - diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml deleted file mode 100644 index 5dd05612..00000000 --- a/.github/workflows/qodana_code_quality.yml +++ /dev/null @@ -1,41 +0,0 @@ -#-------------------------------------------------------------------------------# -# Discover all capabilities of Qodana in our documentation # -# https://www.jetbrains.com/help/qodana/about-qodana.html # -#-------------------------------------------------------------------------------# - -name: Qodana -on: - workflow_dispatch: - pull_request: - branches: - - main - push: - branches-ignore: - - main - -jobs: - qodana: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - checks: write - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - name: 'Qodana Scan' - uses: JetBrains/qodana-action@v2025.2 - env: - QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} - with: - # When pr-mode is set to true, Qodana analyzes only the files that have been changed - pr-mode: false - use-caches: true - post-pr-comment: true - use-annotations: true - # Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job - upload-result: false - # quick-fixes available in Ultimate and Ultimate Plus plans - push-fixes: 'none' From b1c0f9aa884acbe9e08e81a88d0cfa169765e320 Mon Sep 17 00:00:00 2001 From: chan99k Date: Tue, 9 Dec 2025 17:28:45 +0900 Subject: [PATCH 13/13] =?UTF-8?q?Qodana=20=EC=84=A4=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EC=9B=8C=ED=81=AC=ED=94=8C=EB=A1=9C?= =?UTF-8?q?=EC=9A=B0=20=EC=A0=9C=EA=B1=B0**?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - qodana.yaml 파일 삭제 - GitHub 워크플로우에서 Qodana 관련 단계 제거 - Jacoco 코드 커버리지 설정 로직 일부 개선 - 서브프로젝트의 Jacoco exec 파일 처리 시 fileTree 사용 --- .github/workflows/pr-pipeline.yml | 41 +--------------- .../src/main/kotlin/lm.java-infra.gradle.kts | 3 ++ build.gradle.kts | 19 ++++--- gradle/libs.versions.toml | 3 ++ qodana.yaml | 49 ------------------- 5 files changed, 20 insertions(+), 95 deletions(-) delete mode 100644 qodana.yaml diff --git a/.github/workflows/pr-pipeline.yml b/.github/workflows/pr-pipeline.yml index ab03e6cf..79d7e954 100644 --- a/.github/workflows/pr-pipeline.yml +++ b/.github/workflows/pr-pipeline.yml @@ -77,6 +77,7 @@ jobs: - name: Analyze with SonarCloud uses: SonarSource/sonarcloud-github-action@master + # Quality Gate 실패 시 PR 차단을 원하면 아래 줄 제거 continue-on-error: true env: GITHUB_TOKEN: ${{ secrets.SECRET_GITHUB_BOT }} @@ -86,45 +87,7 @@ jobs: -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} -Dsonar.organization=f-lab-edu-1 - # 4. Qodana 정적 분석 (JetBrains) - qodana: - name: Qodana - needs: [ build ] - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - checks: write - security-events: write - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - - name: Qodana Scan - uses: JetBrains/qodana-action@v2025.2 - continue-on-error: true - env: - QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} - with: - # PR 모드: 변경된 파일만 분석하여 속도 향상 - pr-mode: true - use-caches: true - post-pr-comment: true - use-annotations: true - upload-result: true - push-fixes: 'none' - - - name: Upload SARIF to GitHub Security - if: always() - uses: github/codeql-action/upload-sarif@v3 - continue-on-error: true - with: - sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json - - # 5. 자동 리뷰어 할당 + # 4. 자동 리뷰어 할당 auto-assign: name: Auto Assign runs-on: ubuntu-latest diff --git a/build-logic/src/main/kotlin/lm.java-infra.gradle.kts b/build-logic/src/main/kotlin/lm.java-infra.gradle.kts index d4477238..faf1d5b9 100644 --- a/build-logic/src/main/kotlin/lm.java-infra.gradle.kts +++ b/build-logic/src/main/kotlin/lm.java-infra.gradle.kts @@ -12,5 +12,8 @@ dependencies { "runtimeOnly"(catalog.findLibrary("jjwt-impl").get()) "runtimeOnly"(catalog.findLibrary("jjwt-jackson").get()) + // jjwt-impl이 내부적으로 Jackson 어노테이션 사용 - 컴파일 경고 방지 + "compileOnly"(catalog.findLibrary("jackson-annotations").get()) + "implementation"(catalog.findLibrary("slf4j-api").get()) } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f60da0d2..c24e8471 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,11 +20,10 @@ tasks.register("jacocoAggregatedReport") { group = "verification" description = "Generates aggregated Jacoco coverage report for all subprojects" - val jacocoSubprojects = subprojects.filter { subproject -> - subproject.plugins.hasPlugin("jacoco") && - file("${subproject.layout.buildDirectory.get()}/jacoco/test.exec").exists() - } + // jacoco 플러그인이 적용된 서브프로젝트만 필터링 + val jacocoSubprojects = subprojects.filter { it.plugins.hasPlugin("jacoco") } + // 모든 서브프로젝트의 test 태스크에 의존 dependsOn(jacocoSubprojects.map { it.tasks.named("test") }) additionalSourceDirs.setFrom( @@ -36,9 +35,15 @@ tasks.register("jacocoAggregatedReport") { classDirectories.setFrom( jacocoSubprojects.flatMap { it.the()["main"].output } ) - executionData.setFrom( - jacocoSubprojects.map { file("${it.layout.buildDirectory.get()}/jacoco/test.exec") } - ) + + // fileTree는 Execution Phase에서 평가되며, 존재하지 않는 디렉토리는 무시됨 + jacocoSubprojects.forEach { subproject -> + executionData.from( + fileTree(subproject.layout.buildDirectory) { + include("jacoco/test.exec") + } + ) + } reports { xml.required.set(true) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3422ea4e..208427be 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -65,6 +65,9 @@ jjwt-api = { module = "io.jsonwebtoken:jjwt-api", version.ref = "jjwt" } jjwt-impl = { module = "io.jsonwebtoken:jjwt-impl", version.ref = "jjwt" } jjwt-jackson = { module = "io.jsonwebtoken:jjwt-jackson", version.ref = "jjwt" } +# Jackson (Spring Boot BOM에서 버전 관리) +jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations" } + # Password jbcrypt = { module = "org.mindrot:jbcrypt", version.ref = "jbcrypt" } diff --git a/qodana.yaml b/qodana.yaml deleted file mode 100644 index c59d901b..00000000 --- a/qodana.yaml +++ /dev/null @@ -1,49 +0,0 @@ -#-------------------------------------------------------------------------------# -# Qodana analysis is configured by qodana.yaml file # -# https://www.jetbrains.com/help/qodana/qodana-yaml.html # -#-------------------------------------------------------------------------------# - -################################################################################# -# WARNING: Do not store sensitive information in this file, # -# as its contents will be included in the Qodana report. # -################################################################################# -version: "1.0" - -#Specify inspection profile for code analysis -profile: - name: qodana.starter - -#Enable inspections -#include: -# - name: - -#Disable inspections -#exclude: -# - name: -# paths: -# - - -projectJDK: "17" #(Applied in CI/CD pipeline) - - #Execute shell command before Qodana execution (Applied in CI/CD pipeline) - #bootstrap: sh ./prepare-qodana.sh - - #Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) - #plugins: - # - id: #(plugin id can be found at https://plugins.jetbrains.com) - - # Quality gate. Will fail the CI/CD pipeline if any condition is not met - # severityThresholds - configures maximum thresholds for different problem severities - # testCoverageThresholds - configures minimum code coverage on a whole project and newly added code - # Code Coverage is available in Ultimate and Ultimate Plus plans - #failureConditions: - # severityThresholds: - # any: 15 - # critical: 5 - # testCoverageThresholds: - # fresh: 70 - # total: 50 - -#Qodana supports other languages, for example, Python, JavaScript, TypeScript, Go, C#, PHP -#For all supported languages see https://www.jetbrains.com/help/qodana/linters.html -linter: jetbrains/qodana-jvm-community:2025.2