From 23ea900092e56e61bb338705d339f1a6a870bd49 Mon Sep 17 00:00:00 2001 From: Farah Trigui Date: Thu, 5 Mar 2026 11:20:03 +0100 Subject: [PATCH 1/3] feat(): refacto and add yml test ressources --- .github/workflows/validate-schemas.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/validate-schemas.yml b/.github/workflows/validate-schemas.yml index f0bf8b4..0d534e9 100644 --- a/.github/workflows/validate-schemas.yml +++ b/.github/workflows/validate-schemas.yml @@ -4,9 +4,11 @@ on: push: paths: - 'src/main/resources/schemas/**' + - 'src/test/resources' pull_request: paths: - 'src/main/resources/schemas/**' + - 'src/test/resources' workflow_dispatch: jobs: @@ -33,9 +35,21 @@ jobs: - name: Validate YAML examples working-directory: ${{ github.workspace }} run: | - for file in src/main/resources/schemas/{tutorial,examples}/*.{yaml,yml}; do - [ -f "$file" ] || continue - echo "Validating $file..." - npx --prefix .github ajv validate -s src/main/resources/schemas/capability-schema.json -d "$file" --spec=draft2020 --strict-schema=false + PATHS=( + "src/main/resources/schemas/tutorial" + "src/main/resources/schemas/examples" + "src/test/resources" + ) + + for dir in "${PATHS[@]}"; do + for file in "$dir"/*.{yaml,yml}; do + [ -f "$file" ] || continue + echo "Validating $file..." + npx --prefix .github ajv validate \ + -s src/main/resources/schemas/capability-schema.json \ + -d "$file" \ + --spec=draft2020 \ + --strict-schema=false + done done From 13d48ff52db43bceab5bea4efcf8b866d71fe96b Mon Sep 17 00:00:00 2001 From: Farah Trigui Date: Thu, 5 Mar 2026 14:17:52 +0100 Subject: [PATCH 2/3] feat(): adding automated mvn tests and sonarqube --- .github/workflows/Maven_tests.yml | 149 ++++++++++++++++++++++++++++++ pom.xml | 71 +++++++++----- 2 files changed, 197 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/Maven_tests.yml diff --git a/.github/workflows/Maven_tests.yml b/.github/workflows/Maven_tests.yml new file mode 100644 index 0000000..0790c8b --- /dev/null +++ b/.github/workflows/Maven_tests.yml @@ -0,0 +1,149 @@ +name: Maven Tests + SonarQube + +on: + push: + branches: + - 'feat/variablize-and-add-test-yml-ressources' + pull_request: + branches: + - main + +jobs: + test-and-analyze: + name: Unit Tests + JaCoCo + SonarQube + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + + - name: Run tests + JaCoCo coverage + run: mvn test --no-transfer-progress + + - name: Upload JaCoCo report + uses: actions/upload-artifact@v4 + if: always() + with: + name: jacoco-report + path: target/site/jacoco/ + retention-days: 14 + + - name: Start SonarQube + run: | + docker run -d --name sonarqube \ + -p 9000:9000 \ + -e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true \ + sonarqube:community + + - name: Wait for SonarQube + run: | + echo "Waiting for SonarQube..." + for i in $(seq 1 40); do + STATUS=$(curl -s http://localhost:9000/api/system/status 2>/dev/null \ + | grep -o '"status":"[^"]*"' | cut -d'"' -f4) + echo "Attempt $i — status: $STATUS" + [ "$STATUS" = "UP" ] && echo "SonarQube is UP!" && break + sleep 10 + done + + - name: Generate SonarQube token + run: | + # admin:admin est valide au premier démarrage — on en profite pour générer un token + TOKEN_RESP=$(curl -s -u admin:admin -X POST \ + "http://localhost:9000/api/user_tokens/generate" \ + -d "name=ci-token&type=GLOBAL_ANALYSIS_TOKEN") + echo "Response: $(echo $TOKEN_RESP | sed 's/"token":"[^"]*"/"token":"***"/')" + + TOKEN=$(echo "$TOKEN_RESP" | grep -o '"token":"[^"]*"' | cut -d'"' -f4) + echo "Token length: ${#TOKEN}" + echo "$TOKEN" > /tmp/sonar_token.txt + + - name: SonarQube Scan + run: | + SONAR_TOKEN=$(cat /tmp/sonar_token.txt | tr -d '[:space:]') + echo "Token length: ${#SONAR_TOKEN}" + + docker run --rm \ + --network="host" \ + -e SONAR_TOKEN="$SONAR_TOKEN" \ + -v "${{ github.workspace }}:/usr/src" \ + sonarsource/sonar-scanner-cli \ + -Dsonar.projectKey=io.naftiko:framework \ + -Dsonar.sources=src/main/java \ + -Dsonar.java.binaries=target/classes \ + -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml \ + -Dsonar.host.url=http://localhost:9000 + + - name: Wait for analysis to complete + run: sleep 15 + + - name: Export SonarQube report + run: | + mkdir -p sonar-report + AUTH="admin:admin" + PROJECT="io.naftiko:framework" + BASE="http://localhost:9000" + + curl -s -u $AUTH "$BASE/api/issues/search?componentKeys=$PROJECT&ps=500" -o sonar-report/issues.json + curl -s -u $AUTH "$BASE/api/measures/component?component=$PROJECT&metricKeys=bugs,vulnerabilities,code_smells,coverage,duplicated_lines_density,ncloc,sqale_rating,reliability_rating,security_rating" -o sonar-report/measures.json + curl -s -u $AUTH "$BASE/api/qualitygates/project_status?projectKey=$PROJECT" -o sonar-report/quality-gate.json + + QG=$(cat sonar-report/quality-gate.json | grep -o '"status":"[^"]*"' | head -1 | cut -d'"' -f4) + echo "# SonarQube Report — $(date '+%Y-%m-%d %H:%M')" > sonar-report/summary.md + echo "## Quality Gate: $QG" >> sonar-report/summary.md + echo "" >> sonar-report/summary.md + echo "## Measures" >> sonar-report/summary.md + echo '```json' >> sonar-report/summary.md + cat sonar-report/measures.json >> sonar-report/summary.md + echo '```' >> sonar-report/summary.md + + - name: Upload SonarQube report + uses: actions/upload-artifact@v4 + if: always() + with: + name: sonarqube-report + path: sonar-report/ + retention-days: 14 + + - name: Run Trivy scan + run: | + docker run --rm \ + -v "${{ github.workspace }}:/project" \ + aquasec/trivy:latest fs /project \ + --scanners vuln,secret,misconfig \ + --format table \ + --output /project/trivy-report.txt \ + --exit-code 0 + cat trivy-report.txt + + - name: Upload Trivy report + uses: actions/upload-artifact@v4 + if: always() + with: + name: trivy-report + path: trivy-report.txt + retention-days: 14 + + - name: Run Gitleaks scan + run: | + docker run --rm \ + -v "${{ github.workspace }}:/path" \ + zricethezav/gitleaks:latest detect \ + --source /path \ + --report-format json \ + --report-path /path/gitleaks-report.json \ + --exit-code 0 + + - name: Upload Gitleaks report + uses: actions/upload-artifact@v4 + if: always() + with: + name: gitleaks-report + path: gitleaks-report.json + retention-days: 14 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 72ce6f4..ffcf55e 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,6 @@ json-path 2.9.0 - com.fasterxml.jackson.core jackson-core @@ -128,10 +127,12 @@ json-structure 0.5.5 + + org.junit.jupiter junit-jupiter - 6.0.2 + 5.10.2 test @@ -141,24 +142,55 @@ org.apache.maven.plugins maven-compiler-plugin + 3.13.0 - - info.picocli - picocli-codegen - 4.7.6 - + + info.picocli + picocli-codegen + 4.7.6 + - -Aproject=${project.groupId}/${project.artifactId} + -Aproject=${project.groupId}/${project.artifactId} + + org.apache.maven.plugins maven-surefire-plugin - 3.1.2 + 3.2.5 + + + false + + + + + org.jacoco + jacoco-maven-plugin + 0.8.11 + + + prepare-agent + prepare-agent + + + report + test + report + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 4.0.0.4121 + + org.apache.maven.plugins maven-shade-plugin @@ -167,14 +199,11 @@ capability package - - shade - + shade ${project.build.directory}/capability.jar - + io.naftiko.Capability @@ -194,14 +223,11 @@ cli package - - shade - + shade ${project.build.directory}/cli.jar - + io.naftiko.Cli @@ -223,7 +249,7 @@ - + native @@ -236,9 +262,7 @@ build-cli-native - - compile-no-fork - + compile-no-fork package io.naftiko.Cli @@ -254,4 +278,5 @@ + \ No newline at end of file From 47036d02a4686ccb25ff33b1c4316c8e7d56c6df Mon Sep 17 00:00:00 2001 From: Farah Trigui Date: Thu, 5 Mar 2026 16:01:08 +0100 Subject: [PATCH 3/3] feat(): generic way to run tests in VSCode --- .github/workflows/Maven_tests.yml | 149 ----------- .github/workflows/quality-gate.yml | 345 +++++++++++++++++++++++++ .github/workflows/validate-schemas.yml | 4 +- README.md | 7 +- gitleaksignore | 2 + pom.xml | 47 ++-- scripts/pr-check-mac-linux.sh | 88 +++++++ scripts/pr-check-wind.ps1 | 69 +++++ 8 files changed, 538 insertions(+), 173 deletions(-) delete mode 100644 .github/workflows/Maven_tests.yml create mode 100644 .github/workflows/quality-gate.yml create mode 100644 gitleaksignore create mode 100644 scripts/pr-check-mac-linux.sh create mode 100644 scripts/pr-check-wind.ps1 diff --git a/.github/workflows/Maven_tests.yml b/.github/workflows/Maven_tests.yml deleted file mode 100644 index 0790c8b..0000000 --- a/.github/workflows/Maven_tests.yml +++ /dev/null @@ -1,149 +0,0 @@ -name: Maven Tests + SonarQube - -on: - push: - branches: - - 'feat/variablize-and-add-test-yml-ressources' - pull_request: - branches: - - main - -jobs: - test-and-analyze: - name: Unit Tests + JaCoCo + SonarQube - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - cache: maven - - - name: Run tests + JaCoCo coverage - run: mvn test --no-transfer-progress - - - name: Upload JaCoCo report - uses: actions/upload-artifact@v4 - if: always() - with: - name: jacoco-report - path: target/site/jacoco/ - retention-days: 14 - - - name: Start SonarQube - run: | - docker run -d --name sonarqube \ - -p 9000:9000 \ - -e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true \ - sonarqube:community - - - name: Wait for SonarQube - run: | - echo "Waiting for SonarQube..." - for i in $(seq 1 40); do - STATUS=$(curl -s http://localhost:9000/api/system/status 2>/dev/null \ - | grep -o '"status":"[^"]*"' | cut -d'"' -f4) - echo "Attempt $i — status: $STATUS" - [ "$STATUS" = "UP" ] && echo "SonarQube is UP!" && break - sleep 10 - done - - - name: Generate SonarQube token - run: | - # admin:admin est valide au premier démarrage — on en profite pour générer un token - TOKEN_RESP=$(curl -s -u admin:admin -X POST \ - "http://localhost:9000/api/user_tokens/generate" \ - -d "name=ci-token&type=GLOBAL_ANALYSIS_TOKEN") - echo "Response: $(echo $TOKEN_RESP | sed 's/"token":"[^"]*"/"token":"***"/')" - - TOKEN=$(echo "$TOKEN_RESP" | grep -o '"token":"[^"]*"' | cut -d'"' -f4) - echo "Token length: ${#TOKEN}" - echo "$TOKEN" > /tmp/sonar_token.txt - - - name: SonarQube Scan - run: | - SONAR_TOKEN=$(cat /tmp/sonar_token.txt | tr -d '[:space:]') - echo "Token length: ${#SONAR_TOKEN}" - - docker run --rm \ - --network="host" \ - -e SONAR_TOKEN="$SONAR_TOKEN" \ - -v "${{ github.workspace }}:/usr/src" \ - sonarsource/sonar-scanner-cli \ - -Dsonar.projectKey=io.naftiko:framework \ - -Dsonar.sources=src/main/java \ - -Dsonar.java.binaries=target/classes \ - -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml \ - -Dsonar.host.url=http://localhost:9000 - - - name: Wait for analysis to complete - run: sleep 15 - - - name: Export SonarQube report - run: | - mkdir -p sonar-report - AUTH="admin:admin" - PROJECT="io.naftiko:framework" - BASE="http://localhost:9000" - - curl -s -u $AUTH "$BASE/api/issues/search?componentKeys=$PROJECT&ps=500" -o sonar-report/issues.json - curl -s -u $AUTH "$BASE/api/measures/component?component=$PROJECT&metricKeys=bugs,vulnerabilities,code_smells,coverage,duplicated_lines_density,ncloc,sqale_rating,reliability_rating,security_rating" -o sonar-report/measures.json - curl -s -u $AUTH "$BASE/api/qualitygates/project_status?projectKey=$PROJECT" -o sonar-report/quality-gate.json - - QG=$(cat sonar-report/quality-gate.json | grep -o '"status":"[^"]*"' | head -1 | cut -d'"' -f4) - echo "# SonarQube Report — $(date '+%Y-%m-%d %H:%M')" > sonar-report/summary.md - echo "## Quality Gate: $QG" >> sonar-report/summary.md - echo "" >> sonar-report/summary.md - echo "## Measures" >> sonar-report/summary.md - echo '```json' >> sonar-report/summary.md - cat sonar-report/measures.json >> sonar-report/summary.md - echo '```' >> sonar-report/summary.md - - - name: Upload SonarQube report - uses: actions/upload-artifact@v4 - if: always() - with: - name: sonarqube-report - path: sonar-report/ - retention-days: 14 - - - name: Run Trivy scan - run: | - docker run --rm \ - -v "${{ github.workspace }}:/project" \ - aquasec/trivy:latest fs /project \ - --scanners vuln,secret,misconfig \ - --format table \ - --output /project/trivy-report.txt \ - --exit-code 0 - cat trivy-report.txt - - - name: Upload Trivy report - uses: actions/upload-artifact@v4 - if: always() - with: - name: trivy-report - path: trivy-report.txt - retention-days: 14 - - - name: Run Gitleaks scan - run: | - docker run --rm \ - -v "${{ github.workspace }}:/path" \ - zricethezav/gitleaks:latest detect \ - --source /path \ - --report-format json \ - --report-path /path/gitleaks-report.json \ - --exit-code 0 - - - name: Upload Gitleaks report - uses: actions/upload-artifact@v4 - if: always() - with: - name: gitleaks-report - path: gitleaks-report.json - retention-days: 14 \ No newline at end of file diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml new file mode 100644 index 0000000..fdd88fb --- /dev/null +++ b/.github/workflows/quality-gate.yml @@ -0,0 +1,345 @@ +name: Quality Gate + +on: + push: + pull_request: + branches: + - main + workflow_dispatch: +jobs: + test-and-analyze: + name: Unit Tests + JaCoCo + SonarQube + Security + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + + - name: Init summary + run: | + echo "# PR Quality Report — $(date '+%Y-%m-%d %H:%M') UTC" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Tool | Description |" >> $GITHUB_STEP_SUMMARY + echo "|------|-------------|" >> $GITHUB_STEP_SUMMARY + echo "| JaCoCo | Unit test coverage |" >> $GITHUB_STEP_SUMMARY + echo "| SonarQube | Code quality & bugs |" >> $GITHUB_STEP_SUMMARY + echo "| Trivy | CVE vulnerabilities & secrets |" >> $GITHUB_STEP_SUMMARY + echo "| Gitleaks | Secrets in git history |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + + - name: Run tests + JaCoCo coverage + run: mvn clean test --no-transfer-progress + + - name: Publish JaCoCo summary + if: always() + run: | + CSV="target/site/jacoco/jacoco.csv" + { + echo "" + echo "## JaCoCo Coverage" + echo "" + if [ ! -f "$CSV" ]; then + echo "> No coverage report found." + else + MISSED_LINES=$(tail -n +2 "$CSV" | awk -F',' '{sum+=$8} END {print sum+0}') + COVERED_LINES=$(tail -n +2 "$CSV" | awk -F',' '{sum+=$9} END {print sum+0}') + TOTAL=$((MISSED_LINES + COVERED_LINES)) + PCT=$([ "$TOTAL" -gt 0 ] && awk "BEGIN {printf \"%.1f\", ($COVERED_LINES/$TOTAL)*100}" || echo "0.0") + MISSED_B=$(tail -n +2 "$CSV" | awk -F',' '{sum+=$6} END {print sum+0}') + COVERED_B=$(tail -n +2 "$CSV" | awk -F',' '{sum+=$7} END {print sum+0}') + TOTAL_B=$((MISSED_B + COVERED_B)) + PCT_B=$([ "$TOTAL_B" -gt 0 ] && awk "BEGIN {printf \"%.1f\", ($COVERED_B/$TOTAL_B)*100}" || echo "0.0") + echo "| Metric | Value |" + echo "|--------|-------|" + echo "| Line Coverage | **${PCT}%** (${COVERED_LINES} / ${TOTAL}) |" + echo "| Branch Coverage | **${PCT_B}%** (${COVERED_B} / ${TOTAL_B}) |" + fi + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload JaCoCo report + uses: actions/upload-artifact@v4 + if: always() + with: + name: jacoco-report + path: target/site/jacoco/ + retention-days: 14 + + - name: Start SonarQube + run: | + docker run -d --name sonarqube \ + -p 9000:9000 \ + -e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true \ + sonarqube:community + + - name: Wait for SonarQube + run: | + for i in $(seq 1 40); do + STATUS=$(curl -s http://localhost:9000/api/system/status 2>/dev/null \ + | grep -o '"status":"[^"]*"' | cut -d'"' -f4) + echo "Attempt $i — status: $STATUS" + [ "$STATUS" = "UP" ] && echo "SonarQube is UP!" && break + sleep 10 # Wait before retry to allow startup completion + done + + - name: Generate SonarQube token + run: | + curl -s -u admin:admin -X POST \ + "http://localhost:9000/api/user_tokens/generate" \ + -d "name=ci-token&type=GLOBAL_ANALYSIS_TOKEN" \ + | grep -o '"token":"[^"]*"' | cut -d'"' -f4 > /tmp/sonar_token.txt + + - name: SonarQube Scan + run: | + SONAR_TOKEN=$(cat /tmp/sonar_token.txt | tr -d '[:space:]') + docker run --rm \ + --network="host" \ + -e SONAR_TOKEN="$SONAR_TOKEN" \ + -v "${{ github.workspace }}:/usr/src" \ + sonarsource/sonar-scanner-cli \ + -Dsonar.projectKey=io.naftiko:framework \ + -Dsonar.sources=src/main/java \ + -Dsonar.java.binaries=target/classes \ + -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml \ + -Dsonar.host.url=http://localhost:9000 + + - name: Export + Publish SonarQube summary + if: always() + run: | + # Wait 15s for SonarQube to process the analysis results + sleep 15 + mkdir -p sonar-report + AUTH="admin:admin" + PROJECT="io.naftiko:framework" + BASE="http://localhost:9000" + + curl -s -u $AUTH "$BASE/api/measures/component?component=$PROJECT&metricKeys=bugs,vulnerabilities,code_smells,coverage,duplicated_lines_density,ncloc" -o sonar-report/measures.json + curl -s -u $AUTH "$BASE/api/qualitygates/project_status?projectKey=$PROJECT" -o sonar-report/quality-gate.json + curl -s -u $AUTH "$BASE/api/issues/search?componentKeys=$PROJECT&ps=500" -o sonar-report/issues.json + + echo "=== measures.json ===" && cat sonar-report/measures.json + echo "=== quality-gate.json ===" && cat sonar-report/quality-gate.json + + QG=$(python3 -c "import json; d=json.load(open('sonar-report/quality-gate.json')); print(d['projectStatus']['status'])" 2>/dev/null || echo "NONE") + BUGS=$(python3 -c "import json; d=json.load(open('sonar-report/measures.json')); print(next((m['value'] for m in d['component']['measures'] if m['metric']=='bugs'), '0'))" 2>/dev/null || echo "0") + VULNS=$(python3 -c "import json; d=json.load(open('sonar-report/measures.json')); print(next((m['value'] for m in d['component']['measures'] if m['metric']=='vulnerabilities'), '0'))" 2>/dev/null || echo "0") + SMELLS=$(python3 -c "import json; d=json.load(open('sonar-report/measures.json')); print(next((m['value'] for m in d['component']['measures'] if m['metric']=='code_smells'), '0'))" 2>/dev/null || echo "0") + COVERAGE=$(python3 -c "import json; d=json.load(open('sonar-report/measures.json')); print(next((m['value'] for m in d['component']['measures'] if m['metric']=='coverage'), 'N/A'))" 2>/dev/null || echo "N/A") + DUPL=$(python3 -c "import json; d=json.load(open('sonar-report/measures.json')); print(next((m['value'] for m in d['component']['measures'] if m['metric']=='duplicated_lines_density'), 'N/A'))" 2>/dev/null || echo "N/A") + NCLOC=$(python3 -c "import json; d=json.load(open('sonar-report/measures.json')); print(next((m['value'] for m in d['component']['measures'] if m['metric']=='ncloc'), 'N/A'))" 2>/dev/null || echo "N/A") + echo "Parsed: QG=$QG BUGS=$BUGS VULNS=$VULNS SMELLS=$SMELLS COVERAGE=$COVERAGE" + + [ "$QG" = "OK" ] && QG_BADGE="✅ PASSED" || QG_BADGE="❌ FAILED" + + echo "" >> $GITHUB_STEP_SUMMARY + echo "## SonarQube Analysis" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Quality Gate: ${QG_BADGE}**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Bugs | ${BUGS:-0} |" >> $GITHUB_STEP_SUMMARY + echo "| Vulnerabilities | ${VULNS:-0} |" >> $GITHUB_STEP_SUMMARY + echo "| Code Smells | ${SMELLS:-0} |" >> $GITHUB_STEP_SUMMARY + echo "| Coverage | ${COVERAGE:-N/A}% |" >> $GITHUB_STEP_SUMMARY + echo "| Duplications | ${DUPL:-N/A}% |" >> $GITHUB_STEP_SUMMARY + echo "| Lines of Code | ${NCLOC:-N/A} |" >> $GITHUB_STEP_SUMMARY + + cat > /tmp/sonar_metrics.env << EOF + QG=$QG + BUGS=$BUGS + VULNS=$VULNS + SMELLS=$SMELLS + EOF + + - name: Upload SonarQube report + uses: actions/upload-artifact@v4 + if: always() + with: + name: sonarqube-report + path: sonar-report/ + retention-days: 14 + + - name: Run Trivy scan + run: | + docker run --rm \ + -v "${{ github.workspace }}:/project" \ + aquasec/trivy:latest fs /project \ + --scanners vuln,secret,misconfig \ + --format json \ + --output /project/trivy-report.json \ + --exit-code 0 + + - name: Publish Trivy summary + if: always() + run: | + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Trivy Security Scan" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ ! -f trivy-report.json ]; then + echo "> No report found." >> $GITHUB_STEP_SUMMARY + cat > /tmp/trivy_metrics.env << EOF + TRIVY_CRITICAL=0 + TRIVY_HIGH=0 + TRIVY_MEDIUM=0 + TRIVY_LOW=0 + EOF + exit 0 + fi + + CRITICAL=$(grep -o '"Severity":"CRITICAL"' trivy-report.json | wc -l | tr -d ' ') + HIGH=$(grep -o '"Severity":"HIGH"' trivy-report.json | wc -l | tr -d ' ') + MEDIUM=$(grep -o '"Severity":"MEDIUM"' trivy-report.json | wc -l | tr -d ' ') + LOW=$(grep -o '"Severity":"LOW"' trivy-report.json | wc -l | tr -d ' ') + + [ "$CRITICAL" -gt 0 ] && STATUS="❌ ${CRITICAL} critical issue(s)" \ + || { [ "$HIGH" -gt 0 ] && STATUS="⚠️ ${HIGH} high issue(s)" || STATUS="✅ No critical or high issues"; } + + echo "**${STATUS}**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Critical | ${CRITICAL} |" >> $GITHUB_STEP_SUMMARY + echo "| High | ${HIGH} |" >> $GITHUB_STEP_SUMMARY + echo "| Medium | ${MEDIUM} |" >> $GITHUB_STEP_SUMMARY + echo "| Low | ${LOW} |" >> $GITHUB_STEP_SUMMARY + + cat > /tmp/trivy_metrics.env << EOF + TRIVY_CRITICAL=$CRITICAL + TRIVY_HIGH=$HIGH + TRIVY_MEDIUM=$MEDIUM + TRIVY_LOW=$LOW + EOF + + - name: Upload Trivy report + uses: actions/upload-artifact@v4 + if: always() + with: + name: trivy-report + path: trivy-report.json + retention-days: 14 + + - name: Run Gitleaks scan + run: | + docker run --rm \ + -v "${{ github.workspace }}:/path" \ + zricethezav/gitleaks:latest detect \ + --source /path \ + --gitleaks-ignore-path /path/.gitleaksignore \ + --report-format json \ + --report-path /path/gitleaks-report.json \ + --exit-code 0 + + - name: Publish Gitleaks summary + if: always() + run: | + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Gitleaks Secret Scan" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ ! -f gitleaks-report.json ]; then + echo "✅ No secrets detected." >> $GITHUB_STEP_SUMMARY + echo "LEAKS_COUNT=0" > /tmp/gitleaks_metrics.env + exit 0 + fi + + COUNT=$(grep -o '"RuleID"' gitleaks-report.json | wc -l | tr -d ' ') + + if [ "$COUNT" -eq 0 ]; then + echo "✅ No secrets detected." >> $GITHUB_STEP_SUMMARY + else + echo "❌ **${COUNT} secret(s) detected in git history!**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Rule | File | Commit |" >> $GITHUB_STEP_SUMMARY + echo "|------|------|--------|" >> $GITHUB_STEP_SUMMARY + python3 -c " + import json, sys + with open('gitleaks-report.json') as f: + data = json.load(f) + if isinstance(data, list): + for item in data[:10]: + rule = item.get('RuleID', 'N/A') + file = item.get('File', 'N/A') + commit = item.get('Commit', 'N/A')[:8] + print(f'| {rule} | {file} | {commit} |') + " >> $GITHUB_STEP_SUMMARY + fi + + echo "LEAKS_COUNT=$COUNT" > /tmp/gitleaks_metrics.env + + - name: Upload Gitleaks report + uses: actions/upload-artifact@v4 + if: always() + with: + name: gitleaks-report + path: gitleaks-report.json + retention-days: 14 + + - name: Publish badges to Gist + if: always() + env: + GIST_ID: 50bfcb34f6512cbad2dd4f460bfc6526 + GIST_TOKEN: ${{ secrets.GIST_SECRET }} + run: | + source /tmp/sonar_metrics.env 2>/dev/null || true + source /tmp/trivy_metrics.env 2>/dev/null || true + source /tmp/gitleaks_metrics.env 2>/dev/null || true + + # Load JaCoCo coverage + CSV="target/site/jacoco/jacoco.csv" + if [ -f "$CSV" ]; then + MISSED=$(tail -n +2 "$CSV" | awk -F',' '{sum+=$8} END {print sum+0}') + COVERED=$(tail -n +2 "$CSV" | awk -F',' '{sum+=$9} END {print sum+0}') + TOTAL=$((MISSED + COVERED)) + COV_PCT=$([ "$TOTAL" -gt 0 ] && awk "BEGIN {printf \"%.0f\", ($COVERED/$TOTAL)*100}" || echo "0") + [ "$COV_PCT" -ge 80 ] && COV_COLOR="brightgreen" \ + || { [ "$COV_PCT" -ge 50 ] && COV_COLOR="yellow" || COV_COLOR="red"; } + else + COV_PCT="N/A"; COV_COLOR="lightgrey" + fi + + # Sonar Quality Gate badge + [ "${QG:-NONE}" = "OK" ] && QG_MSG="passed" && QG_COLOR="brightgreen" \ + || QG_MSG="failed" && QG_COLOR="red" + + # Sonar bugs badge + [ "${BUGS:-0}" = "0" ] && BUGS_COLOR="brightgreen" || BUGS_COLOR="red" + + # Trivy badge + CRIT="${TRIVY_CRITICAL:-0}"; HIGH_CNT="${TRIVY_HIGH:-0}" + if [ "$CRIT" -gt 0 ]; then + TRIVY_MSG="${CRIT} critical"; TRIVY_COLOR="red" + elif [ "$HIGH_CNT" -gt 0 ]; then + TRIVY_MSG="${HIGH_CNT} high"; TRIVY_COLOR="orange" + else + TRIVY_MSG="clean"; TRIVY_COLOR="brightgreen" + fi + + # Gitleaks badge + LC="${LEAKS_COUNT:-0}" + [ "$LC" -eq 0 ] && LEAKS_MSG="no secrets" && LEAKS_COLOR="brightgreen" \ + || LEAKS_MSG="${LC} secrets" && LEAKS_COLOR="red" + + publish_badge() { + local filename="$1" label="$2" message="$3" color="$4" + local payload="{\"files\":{\"${filename}\":{\"content\":\"{\\\"schemaVersion\\\":1,\\\"label\\\":\\\"${label}\\\",\\\"message\\\":\\\"${message}\\\",\\\"color\\\":\\\"${color}\\\"}\"}}}" + curl -s -X PATCH \ + -H "Authorization: token ${GIST_TOKEN}" \ + -H "Content-Type: application/json" \ + "https://api.github.com/gists/${GIST_ID}" \ + -d "$payload" > /dev/null + echo "Published: $filename → $message ($color)" + } + + publish_badge "framework-coverage.json" "coverage" "${COV_PCT}%" "$COV_COLOR" + publish_badge "framework-quality-gate.json" "quality gate" "$QG_MSG" "$QG_COLOR" + publish_badge "framework-bugs.json" "bugs" "${BUGS:-0}" "$BUGS_COLOR" + publish_badge "framework-trivy.json" "trivy" "$TRIVY_MSG" "$TRIVY_COLOR" + publish_badge "framework-gitleaks.json" "gitleaks" "$LEAKS_MSG" "$LEAKS_COLOR" \ No newline at end of file diff --git a/.github/workflows/validate-schemas.yml b/.github/workflows/validate-schemas.yml index 0d534e9..21ad155 100644 --- a/.github/workflows/validate-schemas.yml +++ b/.github/workflows/validate-schemas.yml @@ -4,11 +4,11 @@ on: push: paths: - 'src/main/resources/schemas/**' - - 'src/test/resources' + - 'src/test/resources/**' pull_request: paths: - 'src/main/resources/schemas/**' - - 'src/test/resources' + - 'src/test/resources/**' workflow_dispatch: jobs: diff --git a/README.md b/README.md index 6f1a66d..b148d54 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # Naftiko Framework -Welcome to Naftiko Framework, the first Open Source project for **Spec-Driven AI Integration**. It reinvents API integration for the AI era with a governed and versatile platform based on capabilities that streamlines API sprawl created by the massive SaaS growth and microservices. +![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/farah-t-trigui/50bfcb34f6512cbad2dd4f460bfc6526/raw/framework-coverage.json) +![Bugs](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/farah-t-trigui/50bfcb34f6512cbad2dd4f460bfc6526/raw/framework-bugs.json) +![Trivy](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/farah-t-trigui/50bfcb34f6512cbad2dd4f460bfc6526/raw/framework-trivy.json) +![Gitleaks](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/farah-t-trigui/50bfcb34f6512cbad2dd4f460bfc6526/raw/framework-gitleaks.json) + +Welcome to Naftiko Framework, the first Open Source project for **Spec-Driven Integration**. It reinvents API integration for the AI era with a governed and versatile platform based on capabilities that streamlines API sprawl created by the massive SaaS growth and microservices. Each capability is a coarse piece of domain that consumes existing HTTP-based APIs, converts into JSON data formats like Protocol Buffer, XML, YAML, CSV and Avro, enabling better Context Engineering and API reusability critical to AI integration. diff --git a/gitleaksignore b/gitleaksignore new file mode 100644 index 0000000..647e5ae --- /dev/null +++ b/gitleaksignore @@ -0,0 +1,2 @@ +# Ignore CI workflow files — admin:admin is a local ephemeral SonarQube instance +[".github/workflows"] diff --git a/pom.xml b/pom.xml index ffcf55e..9d1e744 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,7 @@ json-path 2.9.0 + com.fasterxml.jackson.core jackson-core @@ -128,11 +129,10 @@ 0.5.5 - org.junit.jupiter junit-jupiter - 5.10.2 + 6.0.2 test @@ -157,18 +157,15 @@ - org.apache.maven.plugins maven-surefire-plugin 3.2.5 - false - org.jacoco jacoco-maven-plugin @@ -176,20 +173,19 @@ prepare-agent - prepare-agent + + prepare-agent + report test - report + + report + - - org.sonarsource.scanner.maven - sonar-maven-plugin - 4.0.0.4121 - org.apache.maven.plugins @@ -199,14 +195,18 @@ capability package - shade + + shade + ${project.build.directory}/capability.jar - + io.naftiko.Capability - + @@ -223,14 +223,18 @@ cli package - shade + + shade + ${project.build.directory}/cli.jar - + io.naftiko.Cli - + @@ -248,8 +252,7 @@ - - + native @@ -262,7 +265,9 @@ build-cli-native - compile-no-fork + + compile-no-fork + package io.naftiko.Cli diff --git a/scripts/pr-check-mac-linux.sh b/scripts/pr-check-mac-linux.sh new file mode 100644 index 0000000..a83c360 --- /dev/null +++ b/scripts/pr-check-mac-linux.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# ============================================================ +# pr-check.sh — Run all checks before opening a PR +# Compatible: Mac, Linux (apt/yum/dnf/brew) +# ============================================================ + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +PASS=0 +FAIL=0 + +run_step() { + local label="$1" + shift + echo "" + echo "------------------------------------------------------------" + echo ">>> $label" + echo "------------------------------------------------------------" + if "$@"; then + echo -e "${GREEN}[PASS] $label${NC}" + PASS=$((PASS + 1)) + else + echo -e "${RED}[FAIL] $label${NC}" + FAIL=$((FAIL + 1)) + fi +} + +install_hint() { + local tool="$1" + echo -e "${YELLOW}[SKIP] $tool not installed. Install it with:${NC}" + if [[ "$OSTYPE" == "darwin"* ]]; then + echo " brew install $tool" + elif command -v apt-get &>/dev/null; then + echo " sudo apt-get install -y $tool" + elif command -v dnf &>/dev/null; then + echo " sudo dnf install -y $tool" + elif command -v yum &>/dev/null; then + echo " sudo yum install -y $tool" + elif command -v brew &>/dev/null; then + echo " brew install $tool" + else + echo " Please install $tool manually: https://github.com/$tool" + fi +} + +run_step "Maven clean + unit tests + JaCoCo" \ + mvn clean test --no-transfer-progress + +if command -v trivy &>/dev/null; then + run_step "Trivy - vulnerabilities + secrets + misconfig" \ + trivy fs . --scanners vuln,secret,misconfig --format table --exit-code 0 + trivy fs . --scanners vuln,secret,misconfig --format table > trivy-report.txt 2>&1 || true + echo -e "${YELLOW}Report saved: trivy-report.txt${NC}" +else + install_hint "trivy" + echo " Or visit: https://aquasecurity.github.io/trivy/latest/getting-started/installation/" +fi + +if command -v gitleaks &>/dev/null; then + run_step "Gitleaks - secrets in git history" \ + gitleaks detect --source . --report-format json --report-path gitleaks-report.json --exit-code 0 + echo -e "${YELLOW}Report saved: gitleaks-report.json${NC}" +else + install_hint "gitleaks" + echo " Or visit: https://github.com/gitleaks/gitleaks#installing" +fi + +echo "" +echo "============================================================" +echo " SUMMARY" +echo "============================================================" +echo -e " ${GREEN}PASSED: $PASS${NC}" +if [ $FAIL -gt 0 ]; then + echo -e " ${RED}FAILED: $FAIL${NC}" + echo "" + echo -e "${RED}Fix the issues above before opening a PR.${NC}" + exit 1 +else + echo -e " ${RED}FAILED: 0${NC}" + echo "" + echo -e "${GREEN}All checks passed. Ready to open a PR!${NC}" + exit 0 +fi \ No newline at end of file diff --git a/scripts/pr-check-wind.ps1 b/scripts/pr-check-wind.ps1 new file mode 100644 index 0000000..21731e4 --- /dev/null +++ b/scripts/pr-check-wind.ps1 @@ -0,0 +1,69 @@ +# ============================================================ +# pr-check.ps1 — Run all checks before opening a PR (Windows) +# Usage: .\scripts\pr-check.ps1 +# Requirements: maven, trivy, gitleaks (winget or choco) +# ============================================================ + +$PASS = 0 +$FAIL = 0 + +function Run-Step { + param([string]$Label, [scriptblock]$Command) + Write-Host "" + Write-Host "------------------------------------------------------------" + Write-Host ">>> $Label" + Write-Host "------------------------------------------------------------" + try { + & $Command + if ($LASTEXITCODE -eq 0 -or $LASTEXITCODE -eq $null) { + Write-Host "[PASS] $Label" -ForegroundColor Green + $script:PASS++ + } else { + Write-Host "[FAIL] $Label" -ForegroundColor Red + $script:FAIL++ + } + } catch { + Write-Host "[FAIL] $Label — $_" -ForegroundColor Red + $script:FAIL++ + } +} + +Run-Step "Maven clean + unit tests + JaCoCo" { + mvn clean test --no-transfer-progress +} + +if (Get-Command trivy -ErrorAction SilentlyContinue) { + Run-Step "Trivy - vulnerabilities + secrets + misconfig" { + trivy fs . --scanners vuln,secret,misconfig --format table --exit-code 0 + } + trivy fs . --scanners vuln,secret,misconfig --format table 2>&1 | Out-File -FilePath trivy-report.txt + Write-Host "Report saved: trivy-report.txt" -ForegroundColor Yellow +} else { + Write-Host "[SKIP] Trivy not installed — run: winget install aquasecurity.trivy" -ForegroundColor Yellow +} + +if (Get-Command gitleaks -ErrorAction SilentlyContinue) { + Run-Step "Gitleaks - secrets in git history" { + gitleaks detect --source . --report-format json --report-path gitleaks-report.json --exit-code 0 + } + Write-Host "Report saved: gitleaks-report.json" -ForegroundColor Yellow +} else { + Write-Host "[SKIP] Gitleaks not installed — run: winget install zricethezav.gitleaks" -ForegroundColor Yellow +} + +Write-Host "" +Write-Host "============================================================" +Write-Host " SUMMARY" +Write-Host "============================================================" +Write-Host " PASSED: $PASS" -ForegroundColor Green +if ($FAIL -gt 0) { + Write-Host " FAILED: $FAIL" -ForegroundColor Red + Write-Host "" + Write-Host "Fix the issues above before opening a PR." -ForegroundColor Red + exit 1 +} else { + Write-Host " FAILED: $FAIL" -ForegroundColor Red + Write-Host "" + Write-Host "All checks passed. Ready to open a PR!" -ForegroundColor Green + exit 0 +} \ No newline at end of file