てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] pre-commit を使って git commit 前に自動で ansible-lint する

はじめに

先日の記事で、ansible-lint コマンドの即時性を高めるには別途仕組みが必要な旨を書きました。今回は、自動化の仕組みとして pre-commit を使って仕込んでみます。

だいぶシンプルな設定に抑えています。

導入

venv への pre-commit のインストール

今回は素の vnev を利用します。ansible-lint はインストールされていない状態ではじめます。

$ pip list
Package    Version
---------- -------
pip        23.1
setuptools 53.0.0

venv に pre-commit をインストールします。

$ pip install pre-commit

この時点での pip list は以下のとおりです。

Package      Version
------------ -------
cfgv         3.3.1
distlib      0.3.6
filelock     3.11.0
identify     2.5.22
nodeenv      1.7.0
pip          23.1
platformdirs 3.2.0
pre-commit   3.2.2
PyYAML       6.0
setuptools   53.0.0
virtualenv   20.21.0

リポジトリへの pre-commit のインストール

使いたいリポジトリpre-commit install します。

$ pre-commit install
pre-commit installed at .git/hooks/pre-commit

.git/hooks/pre-commit に処理が入ったのを確認します(おまけ)。venv 名が含まれています。

$ cat .git/hooks/pre-commit
#!/usr/bin/env bash
# File generated by pre-commit: https://pre-commit.com
# ID: 138fd403232d2ddd5efb44317e38bf03

# start templated
INSTALL_PYTHON=/home/sakana/ansible/venvlint/bin/python3
ARGS=(hook-impl --config=.pre-commit-config.yaml --hook-type=pre-commit)
# end templated

HERE="$(cd "$(dirname "$0")" && pwd)"
ARGS+=(--hook-dir "$HERE" -- "$@")

if [ -x "$INSTALL_PYTHON" ]; then
    exec "$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}"
elif command -v pre-commit > /dev/null; then
    exec pre-commit "${ARGS[@]}"
else
    echo '`pre-commit` not found.  Did you forget to activate your virtualenv?' 1>&2
    exit 1
fi

設定ファイルの作成

リポジトリトップに、以下の内容で .pre-commit-config.yaml を作成します。

---
repos:
  - repo: https://github.com/ansible-community/ansible-lint.git
    rev: v6.14.6
    hooks:
      - id: ansible-lint
        files: \.(yaml|yml)$

rev は、ansible/ansible-lint リポジトリタグ一覧から選択します。

例えば v6.14.6 であれば、こちらの .pre-commit-hooks.yaml が対応します。

単体で実行(動作確認)

とりあえず、commit 操作との連動(hook)の前に、pre-commit 単体の動作を確認します。エラーになるような Playbook を予め commit 済みです。

$ pre-commit run
[INFO] Initializing environment for https://github.com/ansible-community/ansible-lint.git.
[INFO] Initializing environment for https://github.com/ansible-community/ansible-lint.git:.,ansible-core>=2.13.3.
[INFO] Installing environment for https://github.com/ansible-community/ansible-lint.git.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
Ansible-lint.............................................................Failed
- hook id: ansible-lint
- exit code: 2
...(略)...

yaml[truthy]: Truthy value should be one of [false, true]
roles/called_include_role/vars/not_called_vars.yml:2

name[missing]: All tasks should be named.
roles/not_called_role/tasks/main.yml:2 Task/Handler: debug msg=Hello!

yaml[truthy]: Truthy value should be one of [false, true]
roles/not_called_role/vars/main.yml:2

Read documentation for instructions on how to ignore specific rule violations.

              Rule Violation Summary              
 count tag           profile rule associated tags 
     6 name[missing] basic   idiom                
     6 yaml[truthy]  basic   formatting, yaml     

Failed after min profile: 12 failure(s), 0 warning(s) on 19 files.

pre-commit 単体で、ansible-lint が実行できたことを確認できました。

ところで、作業用の venv には ansible-lint はインストールしていませんでしたが実行できました。ログの冒頭に Initializing environment あるように、もろもろインストールされたようです。ただし、作業用 venv の pip list は以下のままで、ansible-lint は含まれていません。なので別の場所で管理されているようですが、pre-commit 触り始めのためこのあたりの仕組みはまだよくわかっていないです・・。

$ pip list
Package      Version
------------ -------
cfgv         3.3.1
distlib      0.3.6
filelock     3.11.0
identify     2.5.22
nodeenv      1.7.0
pip          23.1
platformdirs 3.2.0
pre-commit   3.2.2
PyYAML       6.0
setuptools   53.0.0

なお、再度 pre-commit run を実行すると Initializing environment は表示されませんでした。

git commit で実行(本番)

本当にやりたいのはこちらです。git commit 実行時に ansible-lint を hoook する動作を確認します。

エラーありの状態で commit

せっかくなので一つ ansible-lint でエラーになる箇所を追加します。

タスク名に name 内がないというエラーが1箇所ある roles/not_called_role/tasks/main.yml というファイルの

---
- ansible.builtin.debug:
    msg: Hello!

に、FQCN じゃないというエラーを追加します。

---
- debug:
    msg: Hello!

いよいよ commit です。

$ git commit -am "not fqcn"s
Ansible-lint.............................................................Failed
- hook id: ansible-lint
- exit code: 2
...(略)...

fqcn[action-core]: Use FQCN for builtin module actions (debug).
roles/not_called_role/tasks/main.yml:2 Use `ansible.builtin.debug` or `ansible.legacy.debug` instead.

name[missing]: All tasks should be named.
roles/not_called_role/tasks/main.yml:2 Task/Handler: debug msg=Hello!

yaml[truthy]: Truthy value should be one of [false, true]
roles/not_called_role/vars/main.yml:2

Read documentation for instructions on how to ignore specific rule violations.

                 Rule Violation Summary                  
 count tag               profile    rule associated tags 
     6 name[missing]     basic      idiom                
     6 yaml[truthy]      basic      formatting, yaml     
     1 fqcn[action-core] production formatting           

Failed after min profile: 13 failure(s), 0 warning(s) on 18 files.

pre-commit run の実行のときには検出されなかった、roles/not_called_role/tasks/main.yml に対する fqcn[action-core] のエラーも検出されました。commit は完了していません。

commit しようとしたのファイル以外も検出される所もポイントでしょうか。

エラーなしの状態で commit

検出されていたファイルたちを修正して、commit します。

$ git commit -am fix
Ansible-lint.............................................................Passed
...(略)...
$  git log -1
commit 6fb43c55e85920f66b6cd668611ffa8565684269 (HEAD -> main)
Author: Your Name <you@example.com>
Date:   Tue Apr 18 13:17:12 2023 +0000

    fix

ansible-lint が pass し、無事に commit されました。

おわりに

開発者全員に ansible-lint を強制するには、リモートリポジトリ側の CI に組み込む方法がいいと思います。ただ、それだけだと CI の結果を待ってから「あ、しまった。lint で引っかかった」と気づいて fix linting のような commit をして再度 push することになります。できればこの手のものは、今回のような仕組みを利用するなどして、commit 前に解消するのもアリかなと思いました。

なお、今回ちょっとやりきれなかったこととしては、EE 内の ansible-lint を動かすことと、VS Code の git クライアント機能でコミットしたときの表示を見やすくすること、です。

参考記事

ありがとうございます!

qiita.com

maikel.tiny-host.nl

dev.classmethod.jp