initial commit
All checks were successful
Build and Publish TechDocs / build-and-publish (push) Successful in 1m28s
All checks were successful
Build and Publish TechDocs / build-and-publish (push) Successful in 1m28s
Change-Id: Ic85508eb87544e6637b1c2a41c15abc40aa35e0c
This commit is contained in:
209
.gitea/workflows/sonar.yml
Normal file
209
.gitea/workflows/sonar.yml
Normal file
@@ -0,0 +1,209 @@
|
||||
|
||||
name: SonarQube Analysis
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
concurrency:
|
||||
group: ${{ gitea.workflow }}-${{ gitea.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
sonarqube:
|
||||
name: Build, Test & Analyse
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
env:
|
||||
SONAR_PROJECT_KEY: sonar-test-nest4
|
||||
SONAR_ADMIN_TOKEN: ${{ secrets.SONAR_ADMIN_TOKEN }}
|
||||
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
||||
BACKSTAGE_TOKEN: ${{ secrets.BACKSTAGE_TOKEN }}
|
||||
BACKSTAGE_URL: https://dev-andrej.backstage.kyndemo.live
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Node.js
|
||||
run: |
|
||||
if ! command -v node &>/dev/null || [[ "$(node --version | cut -d. -f1 | tr -d 'v')" -lt 18 ]]; then
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
||||
apt-get install -y --no-install-recommends nodejs
|
||||
fi
|
||||
node --version
|
||||
npm --version
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: typescript-nestjs-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: typescript-nestjs-${{ runner.os }}-
|
||||
|
||||
- name: Cache SonarQube analysis data
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: sonar-${{ runner.os }}
|
||||
restore-keys: sonar-${{ runner.os }}-
|
||||
|
||||
# SONAR_TOKEN replaced by SONAR_ADMIN_TOKEN — the admin token is used
|
||||
# only for provisioning; the scan uses an ephemeral PROJECT_ANALYSIS_TOKEN
|
||||
# generated in the bootstrap step below.
|
||||
- name: Validate required secrets
|
||||
run: |
|
||||
[[ -n "$SONAR_ADMIN_TOKEN" ]] || { echo "::error::SONAR_ADMIN_TOKEN is not set"; exit 1; }
|
||||
[[ -n "$SONAR_HOST_URL" ]] || { echo "::error::SONAR_HOST_URL is not set"; exit 1; }
|
||||
[[ -n "$SONAR_PROJECT_KEY" ]] || { echo "::error::SONAR_PROJECT_KEY is not set"; exit 1; }
|
||||
|
||||
SONAR_HOST_URL="${SONAR_HOST_URL%/}"
|
||||
|
||||
AUTH_RESPONSE=$(curl -s -o /tmp/sonar-auth-response.json -w "%{http_code}" \
|
||||
-u "${SONAR_ADMIN_TOKEN}:" \
|
||||
"${SONAR_HOST_URL}/api/authentication/validate")
|
||||
|
||||
if [[ "$AUTH_RESPONSE" != "200" ]]; then
|
||||
echo "::error::SonarQube is unreachable or returned HTTP ${AUTH_RESPONSE} — check SONAR_HOST_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOKEN_VALID=$(jq -r '.valid' /tmp/sonar-auth-response.json 2>/dev/null || echo "false")
|
||||
if [[ "$TOKEN_VALID" != "true" ]]; then
|
||||
echo "::error::SONAR_ADMIN_TOKEN is invalid or has been revoked (SonarQube returned valid=false)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ SONAR_ADMIN_TOKEN verified against ${SONAR_HOST_URL}"
|
||||
|
||||
- name: Bootstrap SonarQube project and generate scan token
|
||||
id: sonar-bootstrap
|
||||
run: |
|
||||
# ---- 1. Create the project if it doesn't already exist ----
|
||||
PROJECT_COUNT=$(curl -sf \
|
||||
-u "${SONAR_ADMIN_TOKEN}:" \
|
||||
"${SONAR_HOST_URL}/api/projects/search?projects=${SONAR_PROJECT_KEY}" \
|
||||
| jq '.paging.total')
|
||||
|
||||
if [[ "$PROJECT_COUNT" == "0" ]]; then
|
||||
echo "Project '${SONAR_PROJECT_KEY}' not found — creating it..."
|
||||
curl -sf -X POST \
|
||||
-u "${SONAR_ADMIN_TOKEN}:" \
|
||||
"${SONAR_HOST_URL}/api/projects/create" \
|
||||
--data-urlencode "name=${SONAR_PROJECT_KEY}" \
|
||||
--data-urlencode "project=${SONAR_PROJECT_KEY}" \
|
||||
--data-urlencode "mainBranch=main" \
|
||||
--data-urlencode "visibility=private"
|
||||
echo "✅ Project created."
|
||||
else
|
||||
echo "✅ Project '${SONAR_PROJECT_KEY}' already exists — skipping creation."
|
||||
fi
|
||||
|
||||
# ---- 2. Revoke any leftover token from a previous failed run ----
|
||||
TOKEN_NAME="gitea-scan-${SONAR_PROJECT_KEY}"
|
||||
curl -sf -X POST \
|
||||
-u "${SONAR_ADMIN_TOKEN}:" \
|
||||
"${SONAR_HOST_URL}/api/user_tokens/revoke" \
|
||||
--data-urlencode "name=${TOKEN_NAME}" \
|
||||
> /dev/null 2>&1 || true
|
||||
|
||||
# ---- 3. Generate a fresh PROJECT_ANALYSIS_TOKEN for this run ----
|
||||
SCAN_TOKEN=$(curl -sf -X POST \
|
||||
-u "${SONAR_ADMIN_TOKEN}:" \
|
||||
"${SONAR_HOST_URL}/api/user_tokens/generate" \
|
||||
--data-urlencode "name=${TOKEN_NAME}" \
|
||||
--data-urlencode "type=PROJECT_ANALYSIS_TOKEN" \
|
||||
--data-urlencode "projectKey=${SONAR_PROJECT_KEY}" \
|
||||
| jq -r '.token')
|
||||
|
||||
[[ -n "$SCAN_TOKEN" ]] || { echo "::error::Failed to generate scan token"; exit 1; }
|
||||
|
||||
echo "✅ Scan token generated for project '${SONAR_PROJECT_KEY}'"
|
||||
echo "::add-mask::${SCAN_TOKEN}"
|
||||
echo "SCAN_TOKEN=${SCAN_TOKEN}" >> "$GITHUB_ENV"
|
||||
- name: Install dependencies and test
|
||||
run: |
|
||||
npm ci
|
||||
npm run test:cov
|
||||
|
||||
- name: SonarQube analysis
|
||||
uses: sonarsource/sonarqube-scan-action@v5
|
||||
env:
|
||||
SONAR_TOKEN: ${{ env.SCAN_TOKEN }}
|
||||
with:
|
||||
args: >
|
||||
-Dsonar.projectKey=sonar-test-nest4
|
||||
-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
|
||||
- name: Quality Gate check
|
||||
id: quality-gate
|
||||
run: |
|
||||
echo "Waiting for SonarQube to process the analysis..."
|
||||
for i in $(seq 1 24); do
|
||||
RESPONSE=$(curl -sf -u "${SCAN_TOKEN}:" \
|
||||
"${SONAR_HOST_URL}/api/qualitygates/project_status?projectKey=${SONAR_PROJECT_KEY}" || true)
|
||||
STATUS=$(echo "$RESPONSE" | jq -r '.projectStatus.status' 2>/dev/null || echo "NONE")
|
||||
if [[ "$STATUS" =~ ^(OK|ERROR|WARN)$ ]]; then break; fi
|
||||
echo " Status: ${STATUS:-pending} — retrying in 5s..."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "══════════════════════════════════════════"
|
||||
echo " Quality Gate: $STATUS"
|
||||
echo "══════════════════════════════════════════"
|
||||
|
||||
echo "$RESPONSE" | jq -r '
|
||||
.projectStatus.conditions[] |
|
||||
if .status == "ERROR" then " ❌ \(.metricKey): \(.actualValue) (threshold: \(.errorThreshold), comparator: \(.comparator))"
|
||||
elif .status == "WARN" then " ⚠️ \(.metricKey): \(.actualValue) (threshold: \(.errorThreshold), comparator: \(.comparator))"
|
||||
else " ✅ \(.metricKey): \(.actualValue)"
|
||||
end'
|
||||
|
||||
echo "══════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
FAILED=$(echo "$RESPONSE" | jq '[.projectStatus.conditions[] | select(.status == "ERROR")] | length')
|
||||
if [[ "$FAILED" -gt 0 ]]; then
|
||||
echo "::error::Quality Gate FAILED — $FAILED metric(s) did not meet threshold"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Notify Backstage on quality gate failure
|
||||
if: always() && steps.quality-gate.outcome == 'failure'
|
||||
run: |
|
||||
echo "Sending quality gate failure notification to Backstage..."
|
||||
HTTP_CODE=$(curl -s -o /tmp/bs-notify-response.json -w "%{http_code}" \
|
||||
-X POST "${BACKSTAGE_URL}/api/notifications" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer ${BACKSTAGE_TOKEN}" \
|
||||
-d "{
|
||||
\"recipients\": {
|
||||
\"type\": \"entity\",
|
||||
\"entityRef\": \"group:default/platform-engineering\"
|
||||
},
|
||||
\"payload\": {
|
||||
\"title\": \"SonarQube Quality Gate Failed\",
|
||||
"description": "Quality gate failed for ${SONAR_PROJECT_KEY} on branch ${GITHUB_HEAD_REF}. Review the SonarQube dashboard for details.",
|
||||
\"link\": \"${SONAR_HOST_URL}/dashboard?id=${SONAR_PROJECT_KEY}\",
|
||||
\"severity\": \"high\",
|
||||
\"topic\": \"sonarqube-quality-gate\"
|
||||
}
|
||||
}")
|
||||
echo "HTTP status: ${HTTP_CODE}"
|
||||
cat /tmp/bs-notify-response.json 2>/dev/null || true
|
||||
if [[ "$HTTP_CODE" -ge 200 && "$HTTP_CODE" -lt 300 ]]; then
|
||||
echo "✅ Backstage notification sent"
|
||||
else
|
||||
echo "⚠️ Backstage notification failed (HTTP ${HTTP_CODE})"
|
||||
fi
|
||||
|
||||
- name: Revoke scan token
|
||||
if: always()
|
||||
run: |
|
||||
curl -sf -X POST \
|
||||
-u "${SONAR_ADMIN_TOKEN}:" \
|
||||
"${SONAR_HOST_URL}/api/user_tokens/revoke" \
|
||||
--data-urlencode "name=gitea-scan-${SONAR_PROJECT_KEY}" \
|
||||
&& echo "✅ Scan token revoked" \
|
||||
|| echo "⚠️ Token revocation failed (may have already been removed)"
|
||||
Reference in New Issue
Block a user