koh’s blog

Sys Admin who loves automation

Ansibleでcommandを使わないためのモジュール

Playbookを書いているとたまに

  • 特定のパッケージがインストールされていたらなにかする
  • 特定のユーザが存在したらなにかする

みたいな処理をしたいときがあります。
実際にインストールしたりユーザ作成せずに現状の確認だけをしたいケースです。

そういったときに command, shellモジュールを使用することで実現できます。

  tasks:
    - name: check if httpd is installed
      shell: rpm -qa | grep httpd
      register: httpd_installed
      ignore_errors: True
      check_mode: False
      changed_when: False

    - name: print
      debug:
        msg: "httpd is installed"
      when: httpd_installed.rc == 0

ただし上記のやり方は色々めんどくさいこともあります。

  • changed_whenignore_errors等のパラメータの設定が必要
  • ansible実行時やansible-lintでwarningが出力されがち

そこでcommandモジュールの代わりになりうるモジュールをいくつか紹介します。

検証環境

ansible: v2.9.9
targetOS: CentOS7.7 and Ubuntu 18.04

stat

stat – Retrieve file or file system status — Ansible Documentation

statモジュールはファイルの状態を確認できます。
ファイルの有無のみでなく種類(ファイル/ディレクトリ/リンクなど)やowner等の情報も取得できます。

  tasks:
    - name: check if /etc/hosts exists
      stat:
        path: /etc/hosts
      register: etchosts

    - name: print
      debug:
        msg: "/etc/hosts exists"
      when: etchosts.stat.exists

    - name: print
      debug:
        msg: "/etc/hosts is directory"
      when: etchosts.stat.isdir # this should be False

package_facts

package_facts – package information as facts — Ansible Documentation

package_factsはパッケージがインストールされているかの確認ができます。
取得したパッケージ一覧はansible_facts.packages に格納されます。

  tasks:
    - name: check packages
      package_facts:
        manager: auto

    - name: print
      debug:
        msg: "Version of NetworkManager is {{ ansible_facts.packages['NetworkManager'][0]['version'] }}"
      when: "'NetworkManager' in ansible_facts.packages"

getent

getent – A wrapper to the unix getent utility — Ansible Documentation

getentモジュールはgetentコマンドのラッパーでpasswdやgroup等のデータを取得できます。
取得した情報はgetent_***に格納されます(***は指定したdatabase名)
※取得できる情報は対象のOSによって異なります。

  tasks:
    - name: check users
      getent:
        database: passwd

    - name: print
      debug:
        msg: "app1 user exists"
      when: "'app1' in getent_passwd"

service_facts

service_facts – Return service state information as fact data — Ansible Documentation

service_factsモジュールは各serviceの情報を取得します。
取得した情報はansible_facts.servicesに格納されます。

  tasks:
    - name: check services
      service_facts:

    - name: print
      debug:
        msg: "firewalld is {{ ansible_facts.services['firewalld.service']['status'] }}"
      when: "'firewalld.service' in ansible_facts.services"

まとめ

たまたまtwitterpackage_factsを知って他にも似たようなモジュールが無いか調べたら色々ありました。
モジュールをなるべく使うことでOSの差分を吸収してくれたりコード量も減るのでよりPlaybookらしいPlaybookになる気がします。

AtCoderで茶色になった記事

お約束のやつです。
先日のABC156でやっと茶色になったのでやったことメモ。

f:id:koh-sh:20200224002749p:plain

やったこと

コンテスト参加

初参加のABC149からは毎回参加しています(計9回)
ABC149が初参加でしたが、ABC149/150は2連unratedだったので若干心折れかけました。

実行環境構築(python)

Atcoderとバージョンを合わせた環境の構築からテストの自動実行まで。
コンテストでの早解きには必須?だと思うし、過去問解くときにもだいぶ効率よく進められます。

koh-sh.hatenablog.com

過去問

f:id:koh-sh:20200224003321p:plain

AtCoder Problemsのスクショです。

大体2ヶ月で150ACくらいです。
主にABCのAからC問題までを順番に解いていっています。 Dは難易度低めであれば解いていますが、解けないことのほうが多いです。

学んだこと

計算量の考え方

計算量(オーダー)の考え方を初めて知った。O(n)など。
これまでは業務上コード実行の速さを考える必要がほとんどなかったので、速度改善の考え方がわかったのは嬉しい。

標準入力の扱い

これまでは大体データは引数かファイルでスクリプトに渡していたので標準入力を扱うのはなんだかんだ初めてだった。

その他

その他AtCoder始めるまで使ったことなかったライブラリ/機能など

  • math
  • itertools
  • fraction
  • collections
  • set
  • pow

今後の予定

数学チックな問題や大きな数(10**9みたいな)が出てくるやつを安定して解けるようにしたい。
ただペルソナを買ってしまったので勉強のペースは少し落ちそう...

Python実行環境構築メモ[Vagrant+Ansible+vscode]

※自分用メモ

AtCoder用のPython実行環境を作る + サンプルのテストの自動化する。
ローカルにpyenvで古いpythonインストールが楽なんだけどイマイチちゃんと動かなかった。

要件

  • AtCoderPythonにバージョンを合わせた実行環境を構築
  • ローカルのmacosには古いpythonはインストールしない (3.4.3)
  • Visual Studio Codeを使えるようにする
  • 各問題のサンプルはコマンド一発でテストできるようにする

手順

vagrantVM作成

Vagrantファイル抜粋

  config.vm.define "vag2" do |node|
    node.vm.box = "generic/ubuntu1804"
    node.vm.hostname = "Vag2"
    node.vm.network :private_network, ip: "192.168.33.12"
    node.vm.provider :virtualbox do |vb|
      vb.customize ["modifyvm", :id, "--memory", "1024"]
      vb.customize ["modifyvm", :id, "--cpus", "1"]
      vb.customize ["modifyvm", :id, "--ioapic", "on"]
    end
    node.vm.synced_folder "./syncdir", "/vagrant",
      create: true, owner: "vagrant", group: "vagrant"
    node.vbguest.auto_update = false
  end

メモ

  • boxは generic/ubuntu1804bento/centos-7.7で確認済み
  • vagrant sshではなくターミナルからsshコマンドで接続する
  • ディレクトリ同期する(local:./syncdir to vm:/vagrant)

pythonインストール

Ansible実行。pyenvでpythonとライブラリをインストール

github.com

sample問題取得

下記ツールをローカルで使用する。超便利。

github.com

同期しているディレクトリをworkspaceにして実行

atcoder-tools gen abc148 --lang python --workspace ~/vag_test/syncdir/atcoder

テスト用シェルもコピーしておく

cp -p /path/to/atcoder-python-role/test.sh ~/vag_test/syncdir/atcoder/.

vscodeVagrantVMに接続

marketplace.visualstudio.com

上記extensionを使用

  1. f1を押して、Remote-SSH: Open SSH Host...を選択、VMSSH

  2. SSH先にてpythonのextensionをインストール

  3. /vagrant/atcoderをOpen

コード書いてテスト

  1. 各問題のディレクトリのmain.pyにコードを記載 (Ex: /vagrant/atcoder/abc148/A/main.py)
  2. テストシェル実行 cd /vagrant/atcoder/ && ./test.sh abc148 A

まとめ

職業柄環境構築や自動化のほうが捗る(過去問解け)

Ansibleのshellモジュールで、falseコマンドを実行できない?

※超小ネタです。

先日Playbookがコケたときの挙動を確認すべく下記のようなtaskを作成しましたが、なぜかsyntaxのエラーが出てしまいました。

% cat test.yml
---
- hosts: Vag1
  gather_facts: False
  tasks:
    - name: fail on purpose
      shell: false
% ansible-playbook test.yml --syntax-check
ERROR! unexpected parameter type in action: <class 'bool'>

The error appears to be in '/Users/koh/vag_test/test.yml': line 5, column 7, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

  tasks:
    - name: fail on purpose
      ^ here

%

原因

原因は false を実行するコマンドとして書いたつもりが、Ansibleにbooleanとして認識されてしまっているようです。
エラー内容を確認するとbooleanを指定すべきではないよ。と出ています。

ERROR! unexpected parameter type in action: <class 'bool'>

shellモジュールは値としてstringを指定してあげる必要があります。

= free_form
        The shell module takes a free form command to run, as a string.
        There is no actual parameter named 'free form'.
        See the examples on how to use this module.

        type: str

※ansible-docの出力抜粋

対策

今回の場合は false をクォーテーションでくくってあげるとstrとして認識されるので問題なくplaybookを実行できます。

[koh@kohs-MBP] ~/vag_test
% cat test.yml
---
- hosts: Vag1
  gather_facts: False
  tasks:
    - name: fail on purpose
      shell: "false"

[koh@kohs-MBP] ~/vag_test
% ansible-playbook test.yml --syntax-check

playbook: test.yml

[koh@kohs-MBP] ~/vag_test
% ansible-playbook test.yml

PLAY [Vag1] *****************************************************************************************

TASK [fail on purpose] ******************************************************************************
fatal: [Vag1]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "false", "delta": "0:00:00.005244", "end": "2020-01-16 13:22:40.832329", "msg": "non-zero return code", "rc": 1, "start": "2020-01-16 13:22:40.827085", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}

PLAY RECAP ******************************************************************************************
Vag1                       : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

[koh@kohs-MBP] ~/vag_test
%

playbookの実行を失敗させるのであればfailモジュールを使用するのもありです。

docs.ansible.com

まとめ

Ansibleがうまく動かないときはだいたい型が間違ってる気がします。(自分の場合)

2019年を雑に振り返る

2019年の出来事を振り返る。
ブログやtwitterを見ながら思い出せる範囲で。

エンジニア関連

ブログ開設

当ブログを開設してちまちま更新している。
また最初は英語でも同じブログに投稿していたが今はdev.toに切り替えた。
内容を振り返ると大体Ansible。

GitHubへのアウトプットを徐々に始める

これまでは職場のPrivateリポジトリのみで使用していたが少しずつ自作のスクリプトやPlaybook等を公開するようにした。
それ以外にも職務経歴書(的なやつ)や読書記録等にも使い始めた。

AtCoder参加(一回だけ)

今年最後のABCになんとなくで参加してみたがunratedとなり灰色になれなかった。
来年からちまちま参加し緑くらいを目指したい。

生活

禁煙

長年タバコを吸い続けていたがずっと喉の調子が良くなく試しに禁煙。
それ以来禁煙は続いていて現在300日以上続いている。
喉の調子はそこまで変わらないが体重はみるみる増えた。

自宅のインターネットをwimaxから固定回線に切替

長らくwimaxを使っていたが通信制限に耐えられずついに切り替えた。
しかし自宅がVDSLでかなり遅い。つらい。
今の自宅の唯一の不満点となる。

主な買ったもの

iPad mini

とても良い。非常に使い勝手がよく通勤中のアマプラ/読書に役立っている。
今年購入したものの中でトップレベルに満足している。

Raspberry Pi3 Model B+

なんとなく物理レイヤーの理解が深まった気がする。
mackerelで監視までしているけれど特に意味なく稼働している。

iPhone11

顔認証を嫌って7のまま我慢していたが意外と快適だった。画面でかくて良い。

switch

リングフィット購入し10年ぶりくらいに楽しく運動できている。

まとめ

エンジニアとしてのアウトプットが増えたことと禁煙が大きいかなーと思う。
そろそろ英語がヤバそうなので来年から本格的にブラッシュアップしたい。

telnetで疎通確認みたいなことをAnsibleでやる

DNSやrouting、firewallまわりの設定が正しくできているかの確認として、telnetやncコマンドで疎通確認するみたいな作業はインフラエンジニアにはよくある光景だと思います。
しかしどちらのコマンドも必ずサーバにインストールされているとは限らないですし、何よりAnsibleで使うには相性が悪そうです。
そんなときにはwait_forモジュールが便利です。

wait_forモジュールとは

docs.ansible.com

このモジュールの主なユースケースとしては

  • Javaなどのアプリをstartしてから実際にポートがLISTEN状態になるまで待つ
  • ログを確認して特定の文字列が書かれるまで待つ

など、非同期なタスクをシーケンシャルに実施したいときに役立ちます。

しかしタイムアウトを短くして使用すればポートレベルの疎通確認ができます。

使用例

※Ansible 2.9.0を使用しています
例として下記のようなPlaybookを作成しました。

% cat test.yml
---
- hosts: Vag1
  gather_facts: False
  tasks:
    - name: check if github.com:22 is accessible
      wait_for:
        host: github.com
        port: 22
        state: started
        delay: 0
        timeout: 1

    - name: check if 192.168.33.12:25 is accessible
      wait_for:
        host: 192.168.33.12
        port: 25
        state: started
        delay: 0
        timeout: 1
        search_regex: Postfix
  • 1つ目のタスクはgithub.comに22ポートで疎通できるかの確認
  • 2つ目のタスクは192.168.33.12に25ポートで疎通できるか、Postfixの文字列が得られるかの確認

実際に実行してみます。

[koh@kohs-MBP] ~/vag_test
% ansible-playbook test.yml

PLAY [Vag1] *****************************************************************************************

TASK [check if github.com:22 is accessible] *********************************************************
ok: [Vag1]

TASK [check if 192.168.33.12:25 is accessible] ******************************************************
ok: [Vag1]

PLAY RECAP ******************************************************************************************
Vag1                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[koh@kohs-MBP] ~/vag_test
%

疎通に問題がなければ結果はいずれもokになります。

次に192.168.33.12(仮メールサーバ)にてfirewalldを起動してアクセスをブロックします。

[vagrant@Vag2] ~
% hostname -I 
10.0.2.15 192.168.33.12
[vagrant@Vag2] ~
% sudo systemctl start firewalld
[vagrant@Vag2] ~
%

再度実行してみます。

[koh@kohs-MBP] ~/vag_test
% ansible-playbook test.yml

PLAY [Vag1] *****************************************************************************************

TASK [check if github.com:22 is accessible] *********************************************************
ok: [Vag1]

TASK [check if 192.168.33.12:25 is accessible] ******************************************************
fatal: [Vag1]: FAILED! => {"changed": false, "elapsed": 1, "msg": "Timeout when waiting for search string Postfix in 192.168.33.12:25"}

PLAY RECAP ******************************************************************************************
Vag1                       : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

zsh: exit 2     ansible-playbook test.yml
[koh@kohs-MBP] ~/vag_test
%

上記のようにエラーとなり疎通できていないことがわかります。

まとめ

Ansible 疎通確認などで検索してもAnsible実行ノードとターゲットノードとの疎通の話しか出てこなかったのでちょっと手こずりました。

参考

devops.stackexchange.com

ansible-lintの設定

ansible-lintというAnsibleのPlaybookの問題点を指摘してくれるツールがあります。

github.com

無駄なスペースを検知してくれたり、Playbookのベストプラクティスに沿わないような記載を指摘してくれます。

[koh@kohs-MBP] ~/work/linttest
% ansible-lint site.yml
[201] Trailing whitespace
site.yml:6
        msg: hello

[403] Package installs should not use latest
site.yml:8
Task/Handler: install httpd

[koh@kohs-MBP] ~/work/linttest
%

デフォルトルールの一覧はこちらです。

docs.ansible.com

Playbookの書き方や運用の仕方によっては特定のルールを無視したり別途ルールを追加したりしたい場合もあると思います。
そのための設定方法についてまとめました。
※ ansible-lintのバージョンは 4.1.0 をもとに説明しています。

コマンドラインでの設定

コマンドラインのオプションでも各ルールを設定できます。

オプション一覧(クリックで展開)

[koh@kohs-MBP] ~/work/linttest
% ansible-lint --help
Usage: ansible-lint [options] playbook.yml [playbook2 ...]

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -L                    list all the rules
  -q                    quieter, although not silent output
  -p                    parseable output in the format of pep8
  --parseable-severity  parseable output including severity of rule
  -r RULESDIR           specify one or more rules directories using one or
                        more -r arguments. Any -r flags override the default
                        rules in
                        /Users/koh/.pyenv/versions/3.7.3/lib/python3.7/site-
                        packages/ansiblelint/rules, unless -R is also used.
  -R                    Use default rules in
                        /Users/koh/.pyenv/versions/3.7.3/lib/python3.7/site-
                        packages/ansiblelint/rules in addition to any extra
                        rules directories specified with -r. There is no need
                        to specify this if no -r flags are used
  -t TAGS               only check rules whose id/tags match these values
  -T                    list all the tags
  -v                    Increase verbosity level
  -x SKIP_LIST          only check rules whose id/tags do not match these
                        values
  --nocolor             disable colored output
  --force-color         Try force colored output (relying on ansible's code)
  --exclude=EXCLUDE_PATHS
                        path to directories or files to skip. This option is
                        repeatable.
  -c C                  Specify configuration file to use.  Defaults to
                        ".ansible-lint"
[koh@kohs-MBP] ~/work/linttest
%

基本的にはコマンドラインでのオプションですべて設定できるのですが、チームでルールを共有したりCIに組み込んだりする際には設定ファイルに指定するケースが多いと思います。
設定ファイルでの記載方法について書いていきます。

設定ファイル

デフォルトでは ./.ansible-lint 、もしくはオプション -c で指定したファイルになります。
公式の例はこのような形になっています。

exclude_paths:
  - ./my/excluded/directory/
  - ./my/other/excluded/directory/
  - ./last/excluded/directory/
parseable: true
quiet: true
rulesdir:
  - ./rule/directory/
skip_list:
  - skip_this_tag
  - and_this_one_too
  - skip_this_id
  - '401'
tags:
  - run_this_tag
use_default_rules: true
verbosity: 1

https://docs.ansible.com/ansible-lint/configuring/configuring.html#configuration-file

それぞれの設定項目について説明していきます。

exclude_paths (--exclude)

チェックから除外するパス/ファイルを指定します。

parseable (-p)

出力の形式を変更できます。
複数行に分かれているものが1行にまとまります。

# parseable: false

[koh@kohs-MBP] ~/work/linttest
% ansible-lint site.yml
[201] Trailing whitespace
/Users/koh/work/linttest/roles/201/tasks/main.yml:4
    msg: hello

[202] Octal file permissions must contain leading zero or be a string
/Users/koh/work/linttest/roles/202/tasks/main.yml:2
Task/Handler: error 202

[203] Most files should not contain tabs
/Users/koh/work/linttest/roles/203/tasks/main.yml:4
    msg: "   tab"

[koh@kohs-MBP] ~/work/linttest
%
# parseable: true

[koh@kohs-MBP] ~/work/linttest
% ansible-lint site.yml
/Users/koh/work/linttest/roles/201/tasks/main.yml:4: [E201] Trailing whitespace
/Users/koh/work/linttest/roles/202/tasks/main.yml:2: [E202] Octal file permissions must contain leading zero or be a string
/Users/koh/work/linttest/roles/203/tasks/main.yml:4: [E203] Most files should not contain tabs
[koh@kohs-MBP] ~/work/linttest
%

quiet (-q)

出力形式を変更できます。
0にはなりませんが少し減ります。

# quiet: true

[koh@kohs-MBP] ~/work/linttest
% ansible-lint site.yml
[201] /Users/koh/work/linttest/roles/201/tasks/main.yml:4
[202] /Users/koh/work/linttest/roles/202/tasks/main.yml:2
[203] /Users/koh/work/linttest/roles/203/tasks/main.yml:4
[koh@kohs-MBP] ~/work/linttest
%

rulesdir (-r)

自作のruleを格納したファイル/ディレクトリを指定できます。
自作ruleについては後述します。

skip_list (-x)

スキップするタグやエラーIDを指定できます。
タグについては -T オプションで一覧を出力できます。

タグ一覧(クリックで展開)

[koh@kohs-MBP] ~/work/linttest
% ansible-lint -T
ANSIBLE0002 ['[201]']
ANSIBLE0004 ['[401]']
ANSIBLE0005 ['[402]']
ANSIBLE0006 ['[303]']
ANSIBLE0007 ['[302]']
ANSIBLE0008 ['[103]']
ANSIBLE0009 ['[202]']
ANSIBLE0010 ['[403]']
ANSIBLE0011 ['[502]']
ANSIBLE0012 ['[301]']
ANSIBLE0013 ['[305]']
ANSIBLE0014 ['[304]']
ANSIBLE0015 ['[104]']
ANSIBLE0016 ['[503]']
ANSIBLE0017 ['[501]']
ANSIBLE0018 ['[101]']
ANSIBLE0019 ['[102]']
behaviour ['[503]']
bug ['[304]']
command-shell ['[305]', '[302]', '[304]', '[306]', '[301]', '[303]']
deprecated ['[105]', '[104]', '[103]', '[101]', '[102]']
formatting ['[104]', '[203]', '[201]', '[204]', '[206]', '[205]', '[202]']
idempotency ['[301]']
idiom ['[601]', '[602]']
metadata ['[701]', '[704]', '[703]', '[702]']
module ['[404]', '[401]', '[403]', '[402]']
oddity ['[501]']
readability ['[502]']
repeatability ['[401]', '[403]', '[402]']
resources ['[302]', '[303]']
safety ['[305]']
task ['[502]', '[503]', '[504]', '[501]']
[koh@kohs-MBP] ~/work/linttest
%

tags (-t)

skip_listの逆でこの値が指定された場合は指定されたタグのみチェックします。
ちなみにskip_listとtags両方に同じ値を設定するとスキップされるみたいです。

use_default_rules (-R)

trueの場合デフォルトのルールが適用されます。
自作のルールのみチェックしたい場合にはfalseに指定します。

verbosity (-v)

出力の詳細度を指定できます。
数値で出力量が変わりそうに見えますが現状0か1以上かでのみ挙動が変わりそうです。

        for file in files:
            if self.verbosity > 0:
                print("Examining %s of type %s" % (file['path'], file['type']))
            matches.extend(self.rules.run(file, tags=set(self.tags),
                           skip_list=self.skip_list))

https://github.com/ansible/ansible-lint/blob/5170c04201e71400421b27255280902c211f8548/lib/ansiblelint/__init__.py#L281

Playbook内での設定

Playbook内でコメントを入れることにより行単位でチェック対象から外すことができます。

例として下記のようなPlaybookを作成します。

[koh@kohs-MBP] ~/work/linttest
% cat site.yml
---
- hosts: all
  tasks:
    - name: install latest httpd
      yum:
        name: httpd
        state: latest

    - name: install mysql
      yum:
        name: mysql
        state: installed
[koh@kohs-MBP] ~/work/linttest
%

このPlaybookに対してansible-lintを実行すると403のエラーが返されます。

[koh@kohs-MBP] ~/work/linttest
% ansible-lint site.yml
[403] Package installs should not use latest
site.yml:4
Task/Handler: install latest httpd

[koh@kohs-MBP] ~/work/linttest
%

どうしてもhttpdは常に最新にしておきたいけど他のパッケージはlatestにはしたくないといったとき(多分無い)には下記のように記載できます。

[koh@kohs-MBP] ~/work/linttest
% cat site.yml
---
- hosts: all
  tasks:
    - name: install latest httpd
      yum:
        name: httpd
        state: latest # noqa 403

    - name: install mysql
      yum:
        name: mysql
        state: installed
[koh@kohs-MBP] ~/work/linttest
%

実際にエラーを返す行に # noqa ID とコメントを入れるとチェック対象から除外されます。

[koh@kohs-MBP] ~/work/linttest
% ansible-lint site.yml
[koh@kohs-MBP] ~/work/linttest
%

自作ルール

ansible-lint用のルールを自作することができます。
READMEにも作成の仕方が記載されていますが、デフォルトルールのスクリプトを参考にするとわかりやすいと思います。

https://github.com/ansible/ansible-lint#creating-custom-rules

https://github.com/ansible/ansible-lint/tree/master/lib/ansiblelint/rules

試しに一つ自作ルールを作成してみます。

適当なディレクトリを作成してルール用のスクリプトを作成します。

[koh@kohs-MBP] ~/work/linttest
% cat origrules/True4BooleanRule.py
from ansiblelint import AnsibleLintRule


class True4BooleanRule(AnsibleLintRule):
    id = '99'
    shortdesc = 'Use "True" for boolean'
    description = 'We should "True" for boolean not "yes" or "true".'
    tags = ['formatting']

    def match(self, file, line):
        return ': yes' in line or ': true' in line
[koh@kohs-MBP] ~/work/linttest
%

PlaybookではbooleanのTrueを指定する際に True 以外にも yes , true なども使用できます。
このルールでは先頭大文字の True に統一するべく yes , true を検知してエラーを出します。
※例のためのスクリプトなのでかなり雑に書いています。使用する際には適宜修正してください。

.ansible-lint に下記のように記載します。

[koh@kohs-MBP] ~/work/linttest
% cat .ansible-lint
rulesdir:
  - ./origrules/
[koh@kohs-MBP] ~/work/linttest
%

site.ymlを下記のようにすると3つのタスクのうち上2つが目論見通りエラーになります。

[koh@kohs-MBP] ~/work/linttest
% cat site.yml
---
- hosts: all
  tasks:
    - name: this should be error
      service:
        name: httpd
        state: started
        enabled: yes

    - name: this should be error too
      service:
        name: mysqld
        state: started
        enabled: true

    - name: this is ok
      service:
        name: haproxy
        state: started
        enabled: True
[koh@kohs-MBP] ~/work/linttest
% ansible-lint site.yml
[99] Use "True" for boolean
site.yml:8
        enabled: yes

[99] Use "True" for boolean
site.yml:14
        enabled: true

[koh@kohs-MBP] ~/work/linttest
%

まとめ

設定ファイルでの各パラメータの説明等が公式ドキュメントに見当たらなかったのでまとめてみました。
自作ルールの作成が思ったよりも簡単だったので色々試してみたいですね。