ちょっと話題の記事

SonarQubeでソースコードの静的解析とレビューを自動化してみる(前編)

ソースコードの静的解析ツールとして人気のSonarQubeを使い、自分のコードのイケてない箇所を分析してみました。
2018.07.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

サーバーレス開発部@大阪の岩田です。 私が現在参画しているプロジェクトでは、GitHubFlowに近いフローで開発を進めています。 おおまかにですが、下記のようなフローです

  1. 各メンバーはmasterブランチをベースに自分がアサインされた機能を開発するためのブランチを作成して開発を進める
  2. 一通りの実装が完了したらプルリクエストを上げる
  3. レビュアーがソースコードレビューを行い、気になる点があればフィードバックし、問題なければmasterブランチにマージ。対象機能の開発が完了する

プロジェクトの中でレビュアーを担当する機会もあるのですが、指摘事項の中には静的解析ツールで機械的に検出できそうなポイントと、人間の目でアナログでチェックしないと検出できなさそうなポイントとに分かれることに気付きます。 例えばですが、

  • 別機能と重複コードが多く、共通化した方が良い
  • メソッドが肥大化しており、まとまった処理の単位で適宜分割した方が良い
  • 未使用変数が存在する

こういった指摘事項はツールを利用してソースコードの静的解析を行うことで自動化できそうです。

今回ソースコードの静的解析にSonarQubeを利用する手法について調査したので、手順をご紹介します。 この前編ではEC2上にSonarQubeServerを構築し、手元のMacでPythonのソースコードをスキャンした結果をUPするところまでを行います。

後編はこちらです。

SonarQubeでソースコードの静的解析とレビューを自動化してみる(後編)

SonarQubeとは?

SonarQubeはオープンソースの品質管理プラットフォームです。

  • 重複コードの検出
  • サイクロマチック数の計測
  • 脆弱性のあるコードの検出
  • バグを誘発しそうなコードの検出
  • 指摘されたissueの管理

等々の機能を持っています。 また、プラグインが豊富で、多くのプログラミング言語をサポートしていることも特徴の1つです。

SonarQubeのアーキテクチャ

SonarQubeは下記の通り4つのコンポーネントが協調して動作します。

コンポーネント 役割
SonarQube Server DatabaseやScannerと連携し、品質指標の管理を行う。また、ユーザー向けにWebUIの提供も行う
SonarQube Database   Serverの情報や諸々の設定、スキャン結果を保存する
SonarQube Plugins Serverの機能を拡張する
SonarQube Scanners ソースコードを解析し、結果をServerに連携する

SonarQubeServerの構築

それではさっそくSonarQubeの環境を構築していきます! まずはEC2上にSonarQubeServerを構築してみます。 今回は下記のような環境を構築していきます。

  • インスタンスタイプ:t2.medium
  • OS:Amazon Linux2
  • DB:postgresql-9.2.23-3.amzn2.x86_64
  • JDK:openjdk:1.8.0_171
  • SonarQube:7.2.1

EC2インスタンスの作成

手順は省略します。 GIPを割り当てつつ、セキュリティグループで22,9000ポートを解放しておいておいて下さい。

Postgresのインストールと初期設定

作成したEC2にまずPostgresのインストールを行います。

sudo yum install postgresql-server
sudo systemctl enable postgresql.service

次にDBクラスタの初期化を行います

sudo su - postgres
initdb --encoding=UTF-8 --no-locale

TCP/IPで接続できるように設定ファイルを修正します

/var/lib/pgsql/data/postgresql.conf

listen_addresses = '0.0.0.0'          # what IP address(es) to listen on;

/var/lib/pgsql/data/pg_hba.conf

host    all             all             0.0.0.0/0               md5

postgresユーザーからec2-userに戻り、サービスを起動します

sudo systemctl start postgresql

SonarQube用のユーザーとデータベースを作成します。

まずはpsqlに入り、、、

psql template1 postgres

psql内で下記のコマンドを実行し、SonarQube用のユーザー、データベースを作成します。 ユーザー、パスワード、データベース全てsonarで作成しています。

create user sonar with password 'sonar' createdb;
\c template1 sonar
create database sonar;

SonarQubeServerのインストール

次にSonarQubeServerのインストールを行います。

SonarQubeユーザーの作成

SonarQube実行用のユーザーを作成しておきます

sudo useradd sonarqube

Javaのインストール

Javaが必要になるのでJavaをインストールします

sudo yum install java-1.8.0-openjdk

OSのパラメータ調整

SonarQubeは内部的にElasticSearchも利用しています。 ElasticSearch向けにカーネルパラメータとulimitの設定を行います。

カーネルパラメータの調整

/etc/sysctl.d/99-sonarqube.conf

fs.file-max=65536
vm.max_map_count=262144
Ulimitの設定

/etc/security/limits.d/99-sonarqube.conf

sonarqube   -   nofile   65536
sonarqube   -   nproc    2048

ここまでできたらOSを再起動してカーネルパラメータの変更を有効化します。

sudo shutdown -r now

SonarQubeのDLと配置

wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-7.2.1.zip -O /tmp/sonarqube.zip
unzip /tmp/sonarqube.zip
sudo mv sonarqube-7.2.1 /usr/local/
sudo chown -R sonarqube:sonarqube /usr/local/sonarqube-7.2.1/
2019/5/9 更新
バイナリの配布元URLが変更されていたため、wgetで指定するURLを更新しました

SonarQubeが先ほど構築したPostgresqlのDBを使用するように設定ファイルを修正します。

/usr/local/sonarqube-7.2.1/conf/sonar.properties

#----- PostgreSQL 8.x or greater
# If you don't use the schema named "public", please refer to http://jira.sonarsource.com/browse/SONAR-5000
sonar.jdbc.url=jdbc:postgresql://localhost/sonar
sonar.jdbc.username=sonar
sonar.jdbc.password=sonar

SonarQubeを起動します。

sudo su sonarqube -c "/usr/local/sonarqube-7.2.1/bin/linux-x86-64/sonar.sh start"

動作確認

ここまでできたら、ブラウザのアドレスバーに http://<EC2のGIP>:9000 と入力してアクセスしてみます。

OKです! ダッシュボードが表示されています。

次に右上のLoginリンクをクリックし、ログインしてみます。 初期のID,パスワードは共にadminです。

ログインすると、ウィザードが起動するので、トークンを作成します。 ここで作成したトークンがScannerを実行する際に必要になるので、忘れずに控えておいて下さい。

次にPython向けの静的解析ルールをカスタマイズしてpylint関連のルールを有効化していきます。 メニューからQuality Profilesを選択し、デフォルト設定のSonar wayをコピーして独自の設定を作成します。

この例ではpython_custom_profileという名前で独自の設定を作成しています。

設定を作成したら、メニューのRulesからPython関連のルールを選択し、Bulk Changeをクリック。 先ほど作成した上記の設定内でルールを有効化します。

ここまでできたら一旦SonarQubeServerの構築は完了です。

SonarQube Scannerの準備

サーバー側が準備できたので、今度はローカルのMac側を準備していきます。 今回構築した環境は下記の通りです。

  • OS:Mac OS X 10.13.5 x86_64
  • JRE:1.8.0_121
  • SonarQube Scanner:3.2.0.1227

まずSonarScannerをDL・解凍します。

wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-3.2.0.1227-macosx.zip
unzip sonar-scanner-cli-3.2.0.1227-macosx.zip
2019/5/9 更新
バイナリの配布元URLが変更されていたため、wgetで指定するURLを更新しました

設定ファイルの準備

スキャン用の設定ファイルsonar-project.propertiesをスキャン対象のプロジェクト直下に用意します。 今回は下記のような設定としました。 SonarQubeServerやSonarScanner単体ではPythonのユニットテストを実行したりカバレッジを取得することはできないので、MAC上でユニットテストとカバレッジの取得を行い、SonarQubeServerにUPします。

sonar-project.properties

sonar.host.url=http://<EC2のGIP>:9000                #SonarQubeServerを指定
sonar.projectKey=pythondemo                         #SonarQubeServerで管理するプロジェクトのキーを指定
sonar.projectName=pythondemo                        #SonarQubeServerのWebUI上で表示するプロジェクトの名前
sonar.sources=.                                     #ソースのパスを指定
sonar.login=xxxxxxxxx                               #作成したトークンを設定
sonar.exclusions=.venv/**,**/tests*.py              #対象外とするファイルを指定

sonar.python.coverage.reportPath=report/cover.xml   #カバレッジレポートのXMLファイル
sonar.python.xunit.reportPath=report/nosetests.xml  #jUnit形式のユニットテストのレポートファイル

django-noseの導入とカバレッジ取得

今回スキャンしたい対象がDjangoで作成したアプリケーションなので、django-noseというライブラリを導入してDjangoアプリのカバレッジを取得できるようにします。

pipenv install --dev django-nose

Djangoのテストがdjango-noseを使用するように設定ファイルを修正します。

INSTALLED_APPS = [
    ...
    'django_nose',
]

TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
    '--with-coverage',
    '--cover-erase',
    '--cover-xml',
    '--cover-xml-file=report/cover.xml',
    '--with-xunit',
    '--xunit-file=report/nosetests.xml',
]

カバレッジのレポートをreport/cover.xmlに、ユニットテストに結果をreport/nosetests.xmlに吐き出すように設定しています。

準備ができたのでテストを実行し、カバレッジとユニットテストのレポートを生成します。

python manage.py test
nosetests batch --with-coverage --cover-package=batch --cover-erase --cover-xml --cover-xml-file=report/cover.xml --with-xunit --xunit-file=report/nosetests.xml --verbosity=1
Creating test database for alias 'default'...
.
.
.

スキャンしてみる

準備ができたので、スキャンを実行してみます。

/Users/xxxxxx/Downloads/sonar-scanner-3.2.0.1227-macosx/bin/sonar-scanner
INFO: Scanner configuration file: /Users/xxxxxx/Downloads/sonar-scanner-3.2.0.1227-macosx/conf/sonar-scanner.properties
INFO: Project root configuration file: /Users/xxxxxx/Documents/xxxxxx/sonar-project.properties
INFO: SonarQube Scanner 3.2.0.1227
INFO: Java 1.8.0_121 Oracle Corporation (64-bit)
INFO: Mac OS X 10.13.5 x86_64
INFO: User cache: /Users/xxxxxx/.sonar/cache
INFO: SonarQube server 7.2.1
INFO: Default locale: "ja_JP", source code encoding: "UTF-8" (analysis is platform dependent)
INFO: Publish mode
INFO: Load global settings
INFO: Load global settings (done) | time=172ms
INFO: Server id: AWTuoUvf50APRC1pYevz
INFO: User cache: /Users/xxxxxx/.sonar/cache
INFO: Load/download plugins
INFO: Load plugins index
INFO: Load plugins index (done) | time=102ms
INFO: Load/download plugins (done) | time=149ms
INFO: Loaded core extensions:
INFO: Process project properties
INFO: Load project repositories
INFO: Load project repositories (done) | time=141ms
INFO: Load quality profiles
INFO: Load quality profiles (done) | time=64ms
INFO: Load active rules
INFO: Load active rules (done) | time=787ms
INFO: Load metrics repository
INFO: Load metrics repository (done) | time=60ms
INFO: Project key: pythondemo
INFO: Project base dir: /Users/xxxxxx/Documents/xxxxxx
INFO: -------------  Scan pythondemo
INFO: Load server rules
INFO: Load server rules (done) | time=114ms
INFO: Base dir: /Users/xxxxxx/Documents/xxxxxx/
INFO: Working dir: /Users/xxxxxx/Documents/xxxxxx/.scannerwork
INFO: Source paths: .
INFO: Source encoding: UTF-8, default locale: ja_JP
INFO: Index files
INFO: Excluded sources:
INFO:   .venv/**
INFO:   **/tests*.py
INFO: 509 files indexed
INFO: 164 files ignored because of inclusion/exclusion patterns
INFO: Quality profile for py: Sonar way
INFO: Quality profile for xml: Sonar way
INFO: Sensor Python Squid Sensor 
.
.
.

しばらく待つとスキャンが完了するので、SonarQubeServerを確認してみます。

スキャン結果を確認してみる

スキャン完了後にダッシュボードにアクセスすると、先ほどスキャンしたプロジェクトがダッシュボードに表示されています。

プロジェクトの詳細に入ると、スキャン結果のサマリを確認することができます。

試しに指摘事項の1つの詳細を確認してみます。

ここでは無意味なif文のネストが指摘されており、どのように修正すれば良いかサンプルも提示されています。

まとめ

SonarQubeを利用したソースコードの静的解析について見てきました。 こういったツールを活用することでシステムの品質、プロジェクトの生産性、共に向上させることが出来るのでは無いでしょうか?

次回はスキャンをCI/CDに組み込みつつ、使い方についてもう少し深掘りしてみようと思います。

参考

SonarQube公式ドキュメント

SonarPython公式ドキュメント

SonarQube SonarPython を 使って Python の静的解析をしてみる