Skip to content

Commit 4931260

Browse files
feat: add SonarQube workflow template for PHP/Drupal projects
- Add sonarqube.yml workflow with self-hosted SonarQube scan - Add sonar-project.properties configuration template - Includes SARIF export to GitHub Security Tab - Update registry to discover .properties files - Allow metadata.id to override derived workflow ID
1 parent b34253a commit 4931260

3 files changed

Lines changed: 175 additions & 6 deletions

File tree

src/core/registry.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,12 @@ export class WorkflowRegistry {
118118
const files = await this.safeReadDir(workflowPath);
119119
if (!files) continue;
120120

121-
const yamlFiles = files.filter((file) => file.endsWith('.yml') || file.endsWith('.yaml'));
122-
if (yamlFiles.length === 0) continue;
121+
const workflowFiles = files.filter((file) =>
122+
file.endsWith('.yml') || file.endsWith('.yaml') || file.endsWith('.properties')
123+
);
124+
if (workflowFiles.length === 0) continue;
123125

124-
const grouped = this.groupByBaseName(yamlFiles);
126+
const grouped = this.groupByBaseName(workflowFiles);
125127
for (const [baseName, variants] of grouped) {
126128
const workflow = await this.parseWorkflow({
127129
category,
@@ -149,8 +151,10 @@ export class WorkflowRegistry {
149151
const files = await this.safeReadDir(typePath);
150152
if (!files) continue;
151153

152-
const yamlFiles = files.filter((file) => file.endsWith('.yml') || file.endsWith('.yaml'));
153-
const grouped = this.groupByBaseName(yamlFiles);
154+
const workflowFiles = files.filter((file) =>
155+
file.endsWith('.yml') || file.endsWith('.yaml') || file.endsWith('.properties')
156+
);
157+
const grouped = this.groupByBaseName(workflowFiles);
154158

155159
for (const [baseName, variants] of grouped) {
156160
const workflowType = this.deriveWorkflowType(baseName, category.id);
@@ -215,7 +219,7 @@ export class WorkflowRegistry {
215219
return weight !== 0 ? weight : a.name.localeCompare(b.name);
216220
});
217221

218-
const workflowId = `${args.category.id}/${args.workflowType}`;
222+
const workflowId = metadata.id ?? `${args.category.id}/${args.workflowType}`;
219223

220224
return {
221225
id: workflowId,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# ---
2+
# id: ci/sonarqube-config
3+
# category: ci
4+
# type: template
5+
# name: SonarQube Configuration
6+
# description: sonar-project.properties configuration file for PHP/Drupal projects
7+
# targetPath: sonar-project.properties
8+
# secrets:
9+
# - name: SONAR_TOKEN
10+
# description: SonarQube authentication token
11+
# required: true
12+
# - name: SONAR_HOST_URL
13+
# description: SonarQube server URL (e.g., https://sonar.example.com)
14+
# required: true
15+
# triggers: []
16+
# variants:
17+
# - name: standard
18+
# description: PHP/Drupal project configuration
19+
# ---
20+
21+
# =============================================================================
22+
# SonarQube Project Configuration for PHP/Drupal
23+
# =============================================================================
24+
# Copy this file to your project root and customize the values below.
25+
# Required secrets in GitHub: SONAR_TOKEN, SONAR_HOST_URL
26+
27+
# Project identification (REQUIRED - change these for your project)
28+
sonar.projectKey=your-project-key
29+
sonar.projectName=Your Project Name
30+
sonar.projectVersion=1.0
31+
32+
# SonarQube server URL (set via SONAR_HOST_URL secret, or uncomment below)
33+
# sonar.host.url=https://sonar.example.com
34+
35+
# Source paths (relative to project root)
36+
sonar.sources=web/modules/custom,web/themes/custom
37+
38+
# Encoding
39+
sonar.sourceEncoding=UTF-8
40+
41+
# Test directories (adjust to match your project structure)
42+
# sonar.tests=web/modules/custom/your_module/tests
43+
44+
# Exclusions - third-party libraries, generated files, and Drupal core/contrib
45+
sonar.exclusions=**/vendor/**,**/node_modules/**,**/libraries/**,**/dist/**
46+
sonar.exclusions+=**/css/**,**/*.min.js,**/*.min.css
47+
sonar.exclusions+=**/tests/**,**/test/**,**/spec/**
48+
sonar.exclusions+=web/core/**,web/modules/contrib/**,web/themes/contrib/**
49+
sonar.exclusions+=web/profiles/contrib/**,web/libraries/**,**/config/**,**/files/**
50+
51+
# PHP-specific settings
52+
sonar.php.file.suffixes=php,module,inc,install,profile,theme
53+
54+
# Coverage exclusions (files that don't need test coverage)
55+
sonar.coverage.exclusions=**/*.xml,**/*.yml,**/*.yaml,**/*.md
56+
57+
# GitHub integration
58+
sonar.pullrequest.provider=github
59+
sonar.pullrequest.github.repository=your-org/your-repo
60+
61+
# Analysis scope
62+
sonar.scm.revision=${env.GITHUB_SHA}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# ---
2+
# id: ci/sonarqube
3+
# category: ci
4+
# type: set
5+
# name: SonarQube Analysis
6+
# description: Self-hosted SonarQube scan with SARIF export to GitHub Security Tab
7+
# targetPath: .github/workflows/sonarqube.yml
8+
# secrets:
9+
# - name: SONAR_TOKEN
10+
# description: SonarQube authentication token
11+
# required: true
12+
# - name: SONAR_HOST_URL
13+
# description: SonarQube server URL (e.g., https://sonar.example.com)
14+
# required: true
15+
# triggers:
16+
# - push
17+
# - pull_request
18+
# variants:
19+
# - name: standard
20+
# description: Self-hosted SonarQube with GitHub Security integration
21+
# ---
22+
23+
name: SonarQube Analysis
24+
25+
on:
26+
push:
27+
branches:
28+
- main
29+
- master
30+
pull_request:
31+
branches:
32+
- main
33+
- master
34+
workflow_dispatch:
35+
36+
concurrency:
37+
group: ${{ github.workflow }}-${{ github.ref }}
38+
cancel-in-progress: true
39+
40+
jobs:
41+
sonarqube-analysis:
42+
name: SonarQube Analysis
43+
runs-on: ubuntu-latest
44+
permissions:
45+
contents: read
46+
security-events: write
47+
checks: write
48+
pull-requests: write
49+
actions: read
50+
steps:
51+
- name: Checkout code
52+
uses: actions/checkout@v4
53+
with:
54+
fetch-depth: 0
55+
56+
- name: Setup PHP
57+
uses: shivammathur/setup-php@v2
58+
with:
59+
php-version: '8.3'
60+
coverage: none
61+
62+
- name: Get Composer Cache Directory
63+
id: composer-cache
64+
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
65+
66+
- name: Cache Composer dependencies
67+
uses: actions/cache@v4
68+
with:
69+
path: ${{ steps.composer-cache.outputs.dir }}
70+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
71+
restore-keys: |
72+
${{ runner.os }}-composer-
73+
74+
- name: Install Composer dependencies
75+
run: composer install --prefer-dist --no-progress --no-suggest --optimize-autoloader
76+
77+
- name: SonarQube Scan
78+
uses: SonarSource/sonarqube-scan-action@v4
79+
env:
80+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
81+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
82+
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
83+
84+
- name: SonarQube → GitHub Security
85+
uses: vmvarela/sonarqube-ce-sarif-action@v1
86+
with:
87+
sonar-host-url: ${{ secrets.SONAR_HOST_URL }}
88+
sonar-token: ${{ secrets.SONAR_TOKEN }}
89+
wait-for-processing: false
90+
processing-delay: 60
91+
92+
- name: Fix SARIF locations
93+
run: |
94+
jq 'walk(if type == "object" and has("results") then .results |= map(select(.locations != null and (.locations | length) > 0)) else . end)' sonarqube.sarif > sonarqube-fixed.sarif
95+
mv sonarqube-fixed.sarif sonarqube.sarif
96+
echo "Filtered SARIF file - removed results without locations"
97+
98+
- name: Upload SARIF to GitHub Security Tab
99+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
100+
uses: github/codeql-action/upload-sarif@v3
101+
with:
102+
sarif_file: sonarqube.sarif
103+
category: sonarqube

0 commit comments

Comments
 (0)