ansible-lint を用いて、コードの記述方法に統一感をもたせた

# はじめに
前回、ansibleでEC2 (Amazon Linux2) のport番号を変更を行いました。
そのコードに対して、ansible-lintをかけて、適切な統一感のあるコードに変更します。

https://qiita.com/hirai-11/private/e73e9396f357e39be8a5

# 現状のディレクトリ構造
```terminal
$ tree
.
├── hosts
├── playbook.yml
└── roles
    └── sshd
        ├── handlers
        │   └── main.yml
        └── tasks
            └── main.yml

4 directories, 4 files
```
# 現状のファイルの内容
```yml:playbook.yml
- name: Change port
  hosts: web
  become: yes
  gather_facts: false
  roles:
    - sshd
```
```yml:roles/sshd/handlers/main.yml
- name: Restart sshd
  service:
    name: sshd
    state: restarted
```
```yml:roles/sshd/tasks/main.yml
# port22 でSSH接続する。タイムアウトは５秒で、接続失敗しても次に進む
- name: Connect default ssh port
  local_action: wait_for port={{ansible_ssh_port}} timeout=5 host={{inventory_hostname}}
  register: default_port
  ignore_errors: true
  become: False

# port22 で失敗した場合、port 50001でSSH接続する。タイムアウトは５秒
- name: Connect custom ssh port
  local_action: wait_for port={{custom_ssh_port}} timeout=5 host={{inventory_hostname}}
  register: custom_port
  when: default_port.elapsed >= 5
  become: False

# port22 でタイムアウトし、port50001でSSH接続成功した場合、ansible_ssh_portを50001に変える
- name: set ansible_ssh_port custom_ssh_port
  set_fact: ansible_ssh_port={{custom_ssh_port}}
  when: default_port.elapsed >= 5 and custom_port.elapsed < 5
  become: False

# 現在port22でSSH接続している場合、/etc/ssh/sshd_configのPortを50001に書き換える
- name: Rewrite custom_ssh_port
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: '^#Port'
    line: 'Port {{custom_ssh_port}}'
  notify: Restart sshd
  when: ansible_ssh_port == 22

# ssh接続時のパスワード認証を無効
- name: Disabling password authentication
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: '^PasswordAuthentication'
    insertafter: '^#PasswordAuthentication'
    line: PasswordAuthentication no
  notify: Restart sshd

# ssh接続時のチャレンジ/レスポンス認証を無効
- name: Disabling Challenge-Response Authentication
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: '^ChallengeResponseAuthentication'
    insertafter: '^#ChallengeResponseAuthentication'
    line: ChallengeResponseAuthentication no
  notify: Restart sshd

# rootユーザーのログイン無効
- name: Disabling root user login
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: '^PermitRootLogin'
    insertafter: '^#PermitRootLogin'
    line: PermitRootLogin no
  notify: Restart sshd
```


# ansible-lintをインストール
Macの場合
```terminal
$ brew install ansible-lint
```
pipでも可
```terminal
$ pip install ansible-lint
```

# ansible-lintを実行してみる

```terminal
% ansible-lint playbook.yml
WARNING  Ignore loading rule from /usr/local/Cellar/ansible-lint/6.9.0/libexec/lib/python3.10/site-packages/ansiblelint/rules/jinja.py due to No module named 'black'
WARNING  Listing 18 violation(s) that are fatal
yaml[truthy]: Truthy value should be one of [false, true]
playbook.yml:3

yaml[new-line-at-end-of-file]: No new line character at the end of file
playbook.yml:6

fqcn[action-core]: Use FQCN for builtin module actions (service).
roles/sshd/handlers/main.yml:1 Use `ansible.builtin.service` or `ansible.legacy.service` instead.

yaml[new-line-at-end-of-file]: No new line character at the end of file
roles/sshd/handlers/main.yml:4

deprecated-local-action: Do not use 'local_action', use 'delegate_to: localhost'.
roles/sshd/tasks/main.yml:2 Task/Handler: Connect default ssh port

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

yaml[truthy]: Truthy value should be one of [false, true]
roles/sshd/tasks/main.yml:6

deprecated-local-action: Do not use 'local_action', use 'delegate_to: localhost'.
roles/sshd/tasks/main.yml:9 Task/Handler: Connect custom ssh port

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

yaml[truthy]: Truthy value should be one of [false, true]
roles/sshd/tasks/main.yml:13

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

name[casing]: All names should start with an uppercase letter. (warning)
roles/sshd/tasks/main.yml:16 Task/Handler: set ansible_ssh_port custom_ssh_port

no-free-form: Avoid using free-form when calling module actions. (set_fact) (warning)
roles/sshd/tasks/main.yml:16 Task/Handler: set ansible_ssh_port custom_ssh_port

yaml[truthy]: Truthy value should be one of [false, true]
roles/sshd/tasks/main.yml:19

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

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

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

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

You can skip specific rules or tags by adding them to your configuration file:
# .config/ansible-lint.yml
warn_list:  # or 'skip_list' to silence them completely
  - deprecated-local-action  # Do not use 'local_action', use 'delegate_to: localhost'.
  - experimental  # all rules tagged as experimental
  - fqcn[action-core]  # Use FQCN for builtin actions.
  - yaml[new-line-at-end-of-file]  # Violations reported by yamllint.
  - yaml[truthy]  # Violations reported by yamllint.

                               Rule Violation Summary                                
 count tag                           profile    rule associated tags                 
     2 deprecated-local-action       basic      deprecations                         
     2 yaml[new-line-at-end-of-file] basic      formatting, yaml                     
     4 yaml[truthy]                  basic      formatting, yaml                     
     1 name[casing]                  moderate   idiom (warning)                      
     1 no-free-form                  moderate   syntax, risk, experimental (warning) 
     8 fqcn[action-core]             production formatting                           

Failed after min profile: 16 failure(s), 2 warning(s) on 3 files.
```
terminalなどカラーになっていて分かりやすいです。
![スクリーンショット 2022-11-27 23.46.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/f1b6fa4e-037e-d0a9-fe4b-010f5b81a92a.png)


16の`failure`と2つの`warning`をもらいました。
これから修正していきます。

## 指摘内容

1. `yaml[truthy]` Truthy value should be one of [false, true]
    - true、falseにしましょう
        - 今回、`False`にしていました。
1. `yaml[new-line-at-end-of-file]`  No new line character at the end of file
    - ファイルの最下部に空行をいれましょう
1. `fqcn[action-core]` Use FQCN for builtin module actions (lineinfile).
    - モジュール名は、FQCN(Fully Qualified Class Nameの略。完全修飾クラス名。)で表記しましょう。
        - `service`ではなく、`ansible.builtin.service`と記載します。
1. `deprecated-local-action` Do not use 'local_action', use 'delegate_to: localhost'.
    - `local_action`ではなく、`delegate_to: localhost`を使いましょう
1. `name[casing]` All names should start with an uppercase letter. 
    - namesは、最初の文字の頭文字を大文字しましょう



# 修正後のファイル
修正後のファイルになります。
```yaml:playbook.yml
- name: Change port
  hosts: web
  become: true
  gather_facts: false
  roles:
    - sshd
```
```yml:roles/sshd/handlers/main.yml
- name: Restart sshd
  ansible.builtin.service:
    name: sshd
    state: restarted

```
```yml:roles/sshd/tasks/main.yml
# port22 でSSH接続する。タイムアウトは５秒で、接続失敗しても次に進む
- name: Connect default ssh port
  ansible.builtin.wait_for:
    port: '{{ansible_ssh_port}}'
    timeout: 5
    host: '{{inventory_hostname}}'
  delegate_to: localhost
  register: default_ssh
  ignore_errors: true
  become: false

# port22 で失敗した場合、port 50001でSSH接続する。タイムアウトは５秒
- name: Connect custom ssh port
  ansible.builtin.wait_for:
    port: '{{custom_ssh_port}}'
    timeout: 5
    host: '{{inventory_hostname}}'
  delegate_to: localhost
  register: high_ssh
  when: default_ssh.elapsed >= 5
  become: false

# port22 でタイムアウトし、port50001でSSH接続成功した場合、ansible_ssh_portを50001に変える
- name: Set ansible_ssh_port custom_ssh_port
  ansible.builtin.set_fact:
    ansible_ssh_port: '{{custom_ssh_port}}'
  when: default_ssh.elapsed >= 5 and high_ssh.elapsed < 5
  become: false

# 現在port22でSSH接続している場合、/etc/ssh/sshd_configのPortを50001に書き換える
- name: Rewrite custom_ssh_port
  ansible.builtin.lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: '^#Port'
    line: 'Port {{custom_ssh_port}}'
  notify: Restart sshd
  when: ansible_ssh_port == 22

# ssh接続時のパスワード認証を無効
- name: Disabling password authentication
  ansible.builtin.lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: '^PasswordAuthentication'
    insertafter: '^#PasswordAuthentication'
    line: PasswordAuthentication no
  notify: Restart sshd

# ssh接続時のチャレンジ/レスポンス認証を無効
- name: Disabling Challenge-Response Authentication
  ansible.builtin.lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: '^ChallengeResponseAuthentication'
    insertafter: '^#ChallengeResponseAuthentication'
    line: ChallengeResponseAuthentication no
  notify: Restart sshd

# rootユーザーのログイン無効
- name: Disabling root user login
  ansible.builtin.lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: '^PermitRootLogin'
    insertafter: '^#PermitRootLogin'
    line: PermitRootLogin no
  notify: Restart sshd

```

修正後は、failureとwarningは0になりました。
```terminal
$ ansible-lint playbook.yml
WARNING  Ignore loading rule from /usr/local/Cellar/ansible-lint/6.9.0/libexec/lib/python3.10/site-packages/ansiblelint/rules/jinja.py due to No module named 'black'

Passed with production profile: 0 failure(s), 0 warning(s) on 3 files.
```

# failureとwarningを出さない方法
以下のように`failure`が１つ表示されていますが、特定の`failure`は、`warning`、もしくは、無視させたい場面がでると思います。
その方法について説明します。
![スクリーンショット 2022-11-27 23.45.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/3ca83c19-cd8e-256f-98a8-a0984d7c7894.png)

まず、ルートディレクトリに`.config/ansible-lint.yml`ファイルを作成します。

```terminal
$ tree
.
├── hosts
├── playbook.yml
└── roles
    └── sshd
        ├── handlers
        │   └── main.yml
        └── tasks
            └── main.yml
$ mkdir .config
$ touch ansible-lint.yml
$ vim ansible-lint.yml
```

特定のルール (`yaml[truthy]`) に対して`failture`から`warning`に変更させたい場合、`.config/ansible-lint.yml`を以下のように変更します。

```yml:.config/ansible-lint.yml
warn_list:
  - yaml[truthy]
```

`failure`から`warning`に変わりました。
![スクリーンショット 2022-11-27 23.53.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/140b017f-da68-0427-8e96-d921402bb61a.png)

無視させたい場合、以下のように記載します。
```yml:.config/ansible-lint.yml
skip_list:
  - yaml[truthy]
```
![スクリーンショット 2022-11-27 23.54.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/3bc14e04-118e-7ce2-a11e-df3d472403b1.png)

`failure`と`warning`が0になりました。

他にも特定のパス配下のみルールを適用しない`exclude_paths`などがあります。
下記を参照して下さい。

https://ansible-lint.readthedocs.io/configuring/

ansible-lintを使うことで、冪等性が担保されない`shell`を使わないように意識付けになったり、コードに統一感をもたせることができたり、よいツールだと感じました！

# 参考

モジュール一覧

https://docs.ansible.com/ansible/latest/collections/ansible/builtin/index.html

https://zenn.dev/y_mrok/books/ansible-no-module-no-tsukaikata

https://zenn.dev/y_mrok/books/ansible-no-tsukaikata

エラー内容

https://qiita.com/ryuichi1208/items/c70b7f88ff4d49cd036c

ansible-lint使い方

https://koh-sh.hatenablog.com/entry/2019/11/03/194100