What's this?

これは次の Ansible Advent Calendar 2020 に参加して書いている記事となります。 他の方の記事については下記のリンクからたどれますので是非あわせてご参照下さい。

Lint を使ってみよう

前々回記事 では Lint がどういうものでなぜ必要かを簡単に紹介しました。 本記事では Ansible Playbook について利用できる代表的な Lint ツールについて使い方を簡単に紹介します。

前準備

ツールの導入を簡単にするために以降では 前回記事 でふれた tox というツールの利用を前提としています。 この記事に書かれていることを試したりする際はまず tox をご用意下さい。

yamllint を使ってみよう

yamllint は pip でインストールできます。 前回記事でふれた tox で試してみましょう。

前準備として次のような tox.ini と tox 内で参照する requirements.txt というファイルを用意しておきます。

[ssato@localhost 03]$ cat requirements.txt
yamllint
[ssato@localhost 03]$ cat tox.ini
[tox]
envlist = py36
skipsdist = true

[testenv]
deps =
    -r {toxinidir}/requirements.txt
commands =
    yamllint --version
    yamllint --help
[ssato@localhost 03]$

tox を使って一旦 yamllint をインストール、ヘルプを表示してみましょう。

[ssato@localhost 03]$ tox
py36 create: /tmp/0/03/.tox/py36
py36 installdeps: -r/tmp/0/03/requirements.txt
py36 installed: pathspec==0.8.1,PyYAML==5.3.1,yamllint==1.25.0
py36 run-test-pre: PYTHONHASHSEED='1153836027'
py36 run-test: commands[0] | yamllint --version
yamllint 1.25.0
py36 run-test: commands[1] | yamllint --help
usage: yamllint [-h] [-] [-c CONFIG_FILE | -d CONFIG_DATA]
                [-f {parsable,standard,colored,github,auto}] [-s]
                [--no-warnings] [-v]
                [FILE_OR_DIR [FILE_OR_DIR ...]]

A linter for YAML files. yamllint does not only check for syntax validity, but
for weirdnesses like key repetition and cosmetic problems such as lines
length, trailing spaces, indentation, etc.

positional arguments:
  FILE_OR_DIR           files to check

optional arguments:
  -h, --help            show this help message and exit
  -                     read from standard input
  -c CONFIG_FILE, --config-file CONFIG_FILE
                        path to a custom configuration
  -d CONFIG_DATA, --config-data CONFIG_DATA
                        custom configuration (as YAML source)
  -f {parsable,standard,colored,github,auto}, --format {parsable,standard,colored,github,auto}
                        format for parsing output
  -s, --strict          return non-zero exit code on warnings as well as
                        errors
  --no-warnings         output only error level problems
  -v, --version         show program's version number and exit
______________________________________ summary _________________________________
  py36: commands succeeded
  congratulations :)
[ssato@localhost 03]$

yamllint の対象ファイルはファイルまたはディレクトリを指定して実行します。 ディレクトリを指定した場合はそのディレクトリ下の指定のパターン [1] に一致する YAML ファイルすべてを再帰的に読込み [2] 、lint を行ないます。

yamllint をより実践的に試すために Ansible Playbook を用意してみましょう。 内容的にあまり意味はないのですがサンプルとして次のようなものを用意してみます。

(py36) [ssato@localhost 03]$ cat 00_ping.yml
- hosts: localhost
  vars:
    foo: true
    bar: yes
  tasks:
    - debug:
        msg: >-
          foo: {{ foo }}
          bar: {{ bar }}

    - ping:

    - name: Collect only facts returned by facter
      setup:
        gather_subset:
          - '!all'
          - '!any'
          - facter
(py36) [ssato@localhost 03]$

ansible-playbook コマンドで --syntax-check し実際に実行しても特に問題はないことがわかります。

[ssato@localhost 03]$ ansible-playbook --syntax-check 00_ping.yml

playbook: 00_ping.yml
(py36) [ssato@localhost 03]$ ansible-playbook 00_ping.yml

PLAY [localhost] ***********************************************************

TASK [Gathering Facts] *****************************************************
ok: [localhost]

TASK [debug] ***************************************************************
ok: [localhost] => {
      "msg": "foo: True bar: True"
}

TASK [ping] ****************************************************************
ok: [localhost]

TASK [Collect only facts returned by facter] *******************************
ok: [localhost]

PLAY RECAP *****************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

(py36) [ssato@localhost 03]$

tox が用意してくれた .tox/ 下にある virutualenv 環境をそのまま使って yamllint を試してみましょう。

(py36) [ssato@localhost 03]$ yamllint 00_ping.yml
00_ping.yml
  1:1       warning  missing document start "---"  (document-start)
  4:10      warning  truthy value should be one of [false, true]  (truthy)

(py36) [ssato@localhost 03]$

何やら二つ警告が表示されましたが次のように変更して修正できます。

(py36) [ssato@localhost 03]$ cp 00_ping.yml{,.save}
(py36) [ssato@localhost 03]$ vi 00_ping.yml
(py36) [ssato@localhost 03]$ diff -u 00_ping.yml{.save,}
--- 00_ping.yml.save    2020-12-03 17:33:39.468022456 +0900
+++ 00_ping.yml 2020-12-03 17:34:24.308412871 +0900
@@ -1,7 +1,8 @@
+---
 - hosts: localhost
   vars:
     foo: true
-    bar: yes
+    bar: "yes"  # または true (真偽値にしたい場合) に変更
   tasks:
     - debug:
         msg: >-
(py36) [ssato@localhost 03]$ yamllint 00_ping.yml; echo $?
0
(py36) [ssato@localhost 03]$

このルールも含めた yamllint でチェック可能な標準ルールについては公式文書もあわせてご参照下さい。

[1]デフォルトでは .yaml, .yml などで終るファイル: https://github.com/adrienverge/yamllint/blob/master/yamllint/config.py#L36
[2]https://github.com/adrienverge/yamllint/blob/master/yamllint/cli.py#L202https://github.com/adrienverge/yamllint/blob/master/yamllint/cli.py#L32

yamllint の設定

yamllint の標準ルールをそのまま適用してすべてパスすれば一番良いのですが現実には難しい場合もあるでしょう。 その場合は特定のルールを無効化するか、ルール毎の細かな設定で対応します。対応方法には大きく二種類あります。

  • 設定ファイルでグローバルに設定
  • 対象ファイルについてファイル全体またはファイルの行単位で設定

それぞれ公式文書に明解な説明がありますのでまずはそちらをご参照下さい。

以下では、順番にそれぞれの設定方法で対応する例をあげていきます。

yamllint の設定ファイル .yamllint で対象ファイルによらずグローバルに設定可能です。 例えば先の yamllint で警告が表示されたファイルについてルールを緩和、無効化することで警告が出なくなります。

(py36) [ssato@localhost 03]$ cat .yamllint
# おまじない (デフォルトの設定をそのまま活用し、一部のみ上書き変更する)
extends: default

# 特定のパスパターンのファイルを検証対象から外す場合
# ignore: |
#   *.molecule/
#   .tox

rules:
  # 文書の開始行をチェックするルールを無効化:
  document-start: disable

  # 一行の中に含まれる文字数の制限のルールを緩和:
  line-length:
    max: 120

  # 真偽値として解釈される値を制限するルールを緩和する設定:
  truthy:
    allowed-values: ['true', 'false', 'yes']
(py36) [ssato@localhost 03]$ yamllint 00_ping.yml.save ; echo $?
0
(py36) [ssato@localhost 03]$

なおこの設定ファイルによる設定方法は、すべての対象ファイルについてルールを緩和、無効化することとなり、やや乱暴な方法ではあります。 そういうわけで、筆者は基本的にはこの方法はおすすめしていません。

次に対象のファイル毎に設定する例ですが次のようにして対応できます。

(py36) [ssato@localhost 03]$ rm .yamllint
(py36) [ssato@localhost 03]$ cp 00_ping.yml.save   00_ping.yml.config_by_comments
(py36) [ssato@localhost 03]$ vi 00_ping.yml.config_by_comments
(py36) [ssato@localhost 03]$ diff -u 00_ping.yml.{save,config_by_comments}
--- 00_ping.yml.save    2020-12-03 17:33:39.468022456 +0900
+++ 00_ping.yml.config_by_comments      2020-12-03 20:24:22.383204640 +0900
@@ -1,6 +1,8 @@
+# yamllint disable rule:document-start
 - hosts: localhost
   vars:
     foo: true
+    # yamllint disable-line rule:truthy
     bar: yes
   tasks:
     - debug:
(py36) [ssato@localhost 03]$ yamllint 00_ping.yml.config_by_comments
(py36) [ssato@localhost 03]$

yamllint の設定でより厳密にチェック

標準ルールの標準設定でもある程度十分とはいえるのですがあくまでもこれは YAML ファイルをチェックするもので Ansible に最適化されているわけではありません。 実際的にするためにはいくつか設定を調整してより厳密なチェックを行うようにすると良いでしょう。

よくある Ansible のコード規約の観点で有効な .yamllint の設定例をいくつかあげておきます [3]

---
extends: default
rules:
  braces:
    forbid: true

  brackets:
    forbid: true

  new-line-at-end-of-file: enable  # ファイル末尾に \n 必須

  new-lines:
    type: unix  # Unix  style (\n のみ) で改行

  octal-values:
    forbid-implicit-octal: true  # 下とあわせて Octal は必ず 0o... 書式で
    forbid-explicit-octal: false

  quoted-strings:
    quote-type: double
    required: only-when-needed

[https://github.com/ssato/yamllint-configuration-examples/blob/main/conf.d/yamllint.ansible-typical]

[3]これは自慢ですが {braces,brackets}.forbid ルールは筆者が機能追加しました: https://github.com/adrienverge/yamllint/pull/319

yamllint によるチェックを CI に組み込む

実際、筆者は yamllint を直接実行することはほぼなく、CI の中か tox (molecule) 経由で実行することがほとんどです。 おすすめは role のテストも実装し molecule を使う方法ですが、すぐには難しい場合は先にあげた例のように tox.ini の commands に列挙されている一部に yamllint . 行を例えば追加し、tox 経由で実行するのが良いでしょう。

tox 経由で実行されるようにしてあればあとは意識することなく CI または tox 実行で一緒に実行されるようにできます。

Ansible Lint を使ってみよう

予定していたのと異なり申し訳ないですが、当初の見込みよりも yamllint だけで結構な量になってしまったので明日以降にします。

次回予告

次回は今日の続きで実際に Ansible Lint をどう使っていくのか実例を示しながら簡単に紹介する予定です。