在 GitLab CI Pipeline 運用 SonarQube 做程式碼品質分析

程式碼品質分析是一項常見會整合在 CI Pipeline 的工具,如果要問目前有哪些免費方案的程式碼分析工具,應該多數人都會回答 SonarQube。

前言

如果問到哪裡有免費的程式碼品質分析(掃描)工具,這幾年大家第一時間多半都會回答 SonarQube Community Edition

(截圖日期:2023.12.04)

如上圖,SonarQube 原廠有提供多種 Edition,商業策略也與其他軟體供應商類似,提供 Community Edition 吸引廣大的使用者,並在 Developer Edition 與 Enterprise Edition 提供更強大的整合與進階功能。

本文就讓我們嘗試架設 SonarQube 9.9.3 LTS,並且在 GitLab CI Pipeline 中加入程式碼分析的 CI Job。

操作步驟

架設 SonarQube Server

老樣子,在這個時代,想要試用一個新服務最簡單的方式,就是想辦法利用 Container 快速將服務架設起來。SonarQube 原廠當然也明白這一點,因此早就準備好現成可用的 Docker image。

這裡我們要使用的 image 是 sonarqube:9.9.3-community

請準備至少有 4GB RAM 可運行 Docker Container 的環境,然後使用下面的 docker-compose.yml 即可快速架設 SonarQube。

services:
  sonarqube:
    # 9.9.3 是 LTS 版本
    image: sonarqube:9.9.3-community
    # SonarQube 需要相依一個 db,這裡我們選用 postgres db
    depends_on:
      - postgres_db
    environment:
    # 設定 postgres 的連線資訊
      SONAR_JDBC_URL: jdbc:postgresql://postgres_db:5432/mysonar
      # 帳密建議可以改一下啦!
      SONAR_JDBC_USERNAME: mysonar
      SONAR_JDBC_PASSWORD: mysonar
      # 如果你的 Container 跑起來有遇到一些 ElasticSearch 的錯誤,可以試著加上這個參數。
      SONAR_ES_BOOTSTRAP_CHECKS_DISABLE: 'true'
    # 主要的 data 直接存放在 docker volumes
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    # 預設使用 9000 port
    ports:
      - "9000:9000"
  postgres_db:
    # 2023.12.5 查看原廠文件,最新支援到 postgres 15
    image: postgres:15
    # 設定 postgresql db 的帳密,要跟前面的 DB 連線資訊一樣喔!
    environment:
      POSTGRES_USER: mysonar
      POSTGRES_PASSWORD: mysonar
    # db 的 data 也直接存放在 docker volumes 之中
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

# 將各個 docker volumes 建起來
volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql:
  postgresql_data:

執行 docker compose up -d 之後,稍待片刻就可以在 port 9000 查看 SonarQube 的 UI 啦!

SonarQube 啟動之後,記得要趕快登入,因為它預設會幫你建立具備 Admin 權限的 User admin,預設密碼是 admin,所以務必要登入並立即修改密碼。

補充說明:務必將 SonarQube 架設在你的 GitLab Runner 能正常連上的地方喔!因為 GitLab Runner 在執行 CI Job 時,會需要將資料傳送給 SonarQube,如果 Runner 連不上 SonarQube,屆時你只會得到連線失敗的錯誤訊息喔!

設定 GitLab 整合

如下圖,SonarQube 可與 Git 版本控制工具整合,其中當然包含了常見的GitLab、Azure DevOps、GitHub。當然這裡我們一定是選 GitLab 啦!

點擊之後就會出現下圖的視窗,讓你輸入 GitLab 的 API URL 與 Personal Access Token。

在產生 Personal Access Token 時要特別注意,如上圖中的提示,你只需要提供在各個 GitLab Project 擁有 Reporter permission 權限之帳號的 Personal Access Token,避免給出太大的權限。

如果 Personal Access Token 有設定成功,接著應該就能看到 SonarQube 可以順利取得你在 GitLab 上的 Project list。

按下右側的 Set up 後,SonarQube 立刻就會自動依據 GitLab Project 建立對應的 SonarQube Project,並且會引導你如何在 CI Pipeline 中建立對應的 CI Job。

嘗試在 CI Pipeline 加入程式碼掃描

延續前面的步驟,SonarQube 會引導該如何在 CI Pipeline 中建立 CI Job。以下我們假設該專案是 Python Code 為例。

首先你要在 GitLab Project 內新增名為 sonar-project.properties 的檔案,這裡會設置一些 SonarQube 需要的參數,如果不想增加此檔案,也可以在執行 Command sonar-scanner 時,自行在指令中帶入參數。更多參數資訊可參閱原廠文件。

接著如下圖,要在 GitLab 的 CI/CD Settings 中新增兩個 Variables。

  1. SONAR_TOKEN,這個 Token 會來自 SonarQube,在上圖的引導中,SonarQube 會建議你單獨 Project 做 Generate a project token,但我知道很多人都會選擇 Generate a global token 讓多個 Project 共用。(基本上這個屬於權限、安全性的議題)
  2. SONAR_HOST_URL,這其實就是你把 SonarQube 架設在哪個 ip 或網址,屆時在 CI Job 執行過程中會連上它。如果你在 GitLab 有建立 Group,可以考慮將這個 Variable 設置在 Group 層級的 CI/CD Settings 中,省去在各個 GitLab Project 做重複設定的時間。

最後一個也是最重要的步驟,我們要在 .gitlab-ci.yml 中,加入 sonarqube-check 這項 CI Job。

下面讓我們仔細看一下 SonarQube 提供的範本。

sonarqube-check:
  image: 
    # 原廠已經有準備好可用的 Docker image
    name: sonarsource/sonar-scanner-cli:latest
    # 覆蓋 image 原有的 ENTRYPOINT
    entrypoint: [""]
  variables:
    # 設置 SonarQube 掃描時產生的 Cache 與暫存檔之存放位置
    SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
    # GIT_DEPTH 是一個進階 CI 技巧,我們留在後面再說明。
    GIT_DEPTH: "0"
  # 進階 CI 技巧,將掃描產生的 Cache 保存至 GitLab CI 的 Cache 功能
  cache:
    # Cache 要給予一個 key,這樣下次執行 Job 時,才知道要取出哪一份 Cache
    key: "${CI_JOB_NAME}"
    paths:
      - .sonar/cache
  script:
    # 原廠 image 已備好 command 可直接使用
    - sonar-scanner
  # 允許此 CI Job 出現 failed。原廠是怕使用者的程式太爛,掃出太多問題嗎?(大誤
  allow_failure: true
  # 限制只有在 main branch 才執行掃描,如果你希望在不同的 branch 也要做掃描,可自行修改。
  only:
    - main

最後,SonarQube 的引導會停在下圖,等著你快點讓 CI Pipeline 成功執行!

如果有成功執行 CI Job,接著在 SonarQube 的 UI,你就能夠看到詳細的掃描結果啦!如下圖,我隨便給的一支 Python code 就被掃出有一個 Code Smell 建議需要修正。

小結

免費版只具備有限的功能與整合

如果你仔細去比較 SonarQube 的功能,你會發現 Community Edition 做到的「整合」真的很少,說是整合,其實只是把 GitLab 的 Token 提供給 SonarQube,讓它有權限透過 GitLab API 去撈出你的 GitLab Project 資訊,方便你可以快速建立 SonarQube 的 Project 以及產出 .gitlab-ci.yml 所需的內容。

只能說免費版功能有限,想要做深度的整合,只能付錢或自行做其他的努力了。 (截圖日期:2023.12.7,來源 www.sonarsource.com

另外,在前面的內容我們有提到,在 SonarQube 引導我們建立 CI Job 時,步驟一它會建議我們在 GitLab Project 內建立 sonar-project.properties 檔案,但其實你也可以選擇不建立它,而是改為在執行 sonar-scanner 時,補上更多的參數,下面舉一個簡單的範例。

  # 我就只截取 .gitlab-ci.yml 的其中一段內容來說明
  script:
  # 原廠的範例只有直接執行 sonar-scanner
    #- sonar-scanner
  # 可以改成類似下面的模樣,用 -D 帶入參數
    - sonar-scanner -Dsonar.projectKey=<你的_sonarqube_project_key>
  # 如果參數很多,就用多個 -D

補充說明 GIT_DEPTH

最後,說明前面沒解釋的 GIT_DEPTH: "0"

大家應該都知道,GitLab Runner 在執行 CI Job 時,在 Job 的執行環境中,它會先去取得存放在 GitLab Project 內的檔案。在這個步驟 Runner 其實也是透過 git 來進行這項操作。

因此 GIT_DEPTH 這個參數就是在控制 Runner 執行 git fetch --depth= 時,後面的 --depth= 要接多大的數字。在一般的狀況下,我們可以不用自行設定 GIT_DEPTH,因為 GitLab 有設好預設值 20

可是隨著你的 Project 越來越肥大,或者是你開始計較 CI Pipeline 的執行時間時,--depth= 設定的數字大小,就變成了其中一個可以縮短 CI Job 執行時間的地方了。在這樣的狀況下,有些人會嘗試調整 GIT_DEPTH,也許從 20 改為 10

也就是說,透過修改 git fetch --depth=,藉此自行控制 Runner 在執行此 CI Job 時,要下載的 Commits 歷史記錄之數量,藉此達到加速的效果;白話一點就是「嘿!Runner 你別浪費時間下載一堆你用不到的東西,我已經幫你想好了,以後你只需要下載這些數量的東西」。

所以按上面的邏輯,那設定 GIT_DEPTH: "0" 是不是代表我們告訴 Runner,請你啥都不要下載呢?

錯!因為 GIT_DEPTH: "0" 代表的是另一個意義,意味著不要使用 --depth,讓我們直接用圖片示範差異。

如上圖,我弄了一個有多條 Branch 與 Commits 的 Project 當範例測試 GIT_DEPTH: "0"GIT_DEPTH: 1 的差異。很明顯的可以看到在 GIT_DEPTH: "0" 的狀況下,由於執行的 Git 指令會是 git fetch,因此下載了多個 branch 的記錄;反之 GIT_DEPTH: 1,執行的是 git fetch --depth=1 於是只有下載了 develop branch 最新一次的 Commit 記錄。

瞭解了 GIT_DEPTH: "0" 帶來的差異之後,最後一個問題就是為何 SonarQube 希望我們設置 GIT_DEPTH: "0"

答案也很單純,因為 SonarQube 希望拿到完整的 Commits 歷史紀錄,這樣它才能幫你做出完整的程式碼分析。

(在 Sonar Community 也有人問類似的問題。)

最後再次提醒,SonarQube 可以掃描的內容很多,可以設定的參數也很多,本文示範的只是最簡單的設置,如果你有更多進階需求,記得查看原廠文件,做出最適合你的配置喔!

參考資料

轉貼本文時禁止修改,禁止商業使用,並且必須註明來自「艦長,你有事嗎?」原創作者 Cheng Wei Chen,及附上原文連結。

工商服務

更多文章