AnsibleでFortiGateの設定を追記するplaybookを作ってみた

# はじめに
AnsibleでFortiGateの設定追加を自動化しようと考えたが、どうやら良きに計らって既存の状態に追加されるわけでなく記載した「あるべき姿」に設定されてしまうため、追加する方法を（なかば強引に）実装してみた。

# 前提
この記事では、以下の要件を実現するためのものである。
* ユーザー（ローカルアカウント）の作成
* 既存のグループ（type:firewall）に作成したユーザーを追加

# 仕組み
追加先となる既存グループのユーザー情報を取得後、ユーザーリスト作成し設定用playbookを作成する流れである。
詳細は以下の通りとなる。

1. ユーザーを作成する
1. 追加先グループのメンバー情報を取得する
1. グループのユーザーリストファイル作成する
1. 上記ステップで作成したユーザーリストファイルに1で作成したユーザー名を追記する
1. グループ設定用playbookテンプレートへユーザーリストの中身を展開しplaybook作成する
1. ユーザーリストを削除する
1. 設定用playbookを実行する

# コード類
## フォルダ構造
```zsh
marseille@Altair 100_Ansible % tree fortigate_mod_group
fortigate_mod_group
├── ansible.cfg
├── inventory
│   └── hosts
├── log
├── playbook
│   └── add_user_and_add_group.yml
├── templates
│   └── add_user_group.jinja
└── tmp

5 directories, 4 files
```

## playbook
```yaml:playbook/add_user_and_add_group.yml
---
# https://ansible-galaxy-fortios-docs.readthedocs.io/en/latest/index.html
# https://ansible-galaxy-fortios-docs.readthedocs.io/en/latest/fortios_monitor.html
- name: FortiGateへのユーザー追加およびグループへの追加
  gather_facts: True
  hosts: LAB
  connection: httpapi
  collections:
    - fortinet.fortios

  vars:
    # ---- shared parameter ---- #
    vdom: "root"
    group_name: "31JFS"
    username: "testuser2"
    password: "testuser"

    # ---- fortios module parameter ---- #
    ansible_httpapi_port: 443
    ansible_httpapi_use_ssl: true
    ansible_httpapi_validate_certs: false

    # ---- role parameter ---- #
    src_role: "templates/add_user_group.jinja"
    dest_role: "playbook/add_user_group.yml"

    # ---- group parameter ---- #
    user_list: "tmp/users.txt"
    users: "{{lookup('file', '{{ ansible_env.PWD }}/{{ user_list }}').splitlines()}}"

  tasks:
    - name: ユーザー追加
      fortios_user_local:
        vdom: "{{ vdom }}"
        state: present
        user_local:
          name: "{{ username }}"
          passwd: "{{ password }}"
          status: "enable"
          two_factor: "disable"
          type: "password"

    - name: 追加先グループのメンバー情報取得
      fortios_configuration_fact:
        vdom: "{{ vdom }}"
        selector: user_group
        params: 
          name: "{{ group_name }}"
      register: get_object

    - name: ----- LOCAL WORK ------
      delegate_to: localhost
      block:
      - name: グループのユーザーリストファイル作成
        lineinfile:
          path: "{{ ansible_env.PWD }}/{{ user_list }}"
          line: "{{ item.1.name }}"
          create: yes
          owner: marseille
          mode: 0644
        with_subelements:
          - "{{ get_object.meta.results }}"
          - member
      - name: ユーザーリストファイルに新規追加ユーザーを追加
        lineinfile:
          path: "{{ ansible_env.PWD }}/{{ user_list }}"
          line: "{{ username }}"
      - name: ユーザーリストをもとに設定用playbookを作成
        template:
          src: "{{ ansible_env.PWD }}/{{ src_role }}"
          dest: "{{ ansible_env.PWD }}/{{ dest_role }}"
          owner: marseille
          mode: 0644
      - name: ユーザリストファイルの削除
        file:
          path: "{{ ansible_env.PWD }}/{{ user_list }}"
          state: absent

    - name: 設定用playbookをロード
      include_tasks: "{{ ansible_env.PWD }}/{{ dest_role }}"

```

## template
```jinja:templates/add_user_group.jinja
---
- name: 設定用playbookを実行
  fortios_user_group:
    vdom: {{ vdom }}
    state: present
    user_group:
      name: {{ group_name }}
      group_type: firewall
      member:
{% for user in users %}
      - name: "{{ user }}"
{% endfor %}
```

# 実行結果
```log
2022-03-07 21:49:00,529 p=13930 u=marseille n=ansible | PLAY [FortiGateへのユーザー追加およびグループへの追加] **************************************************************************************************************************************************************************************
2022-03-07 21:49:00,542 p=13930 u=marseille n=ansible | TASK [Gathering Facts] **********************************************************************************************************************************************************************************************************************
2022-03-07 21:49:01,812 p=13934 u=marseille n=ansible | platform_type is set to fortinet.fortios.fortios
2022-03-07 21:49:02,912 p=13930 u=marseille n=ansible | ok: [FG60E]
2022-03-07 21:49:02,931 p=13930 u=marseille n=ansible | TASK [ユーザー追加] *************************************************************************************************************************************************************************************************************************
2022-03-07 21:49:05,135 p=13930 u=marseille n=ansible | changed: [FG60E]
2022-03-07 21:49:05,145 p=13930 u=marseille n=ansible | TASK [追加先グループのメンバー情報取得] *****************************************************************************************************************************************************************************************************
2022-03-07 21:49:06,909 p=13930 u=marseille n=ansible | ok: [FG60E]
2022-03-07 21:49:06,927 p=13930 u=marseille n=ansible | TASK [グループのユーザーリストファイル作成] *************************************************************************************************************************************************************************************************
2022-03-07 21:49:07,405 p=13930 u=marseille n=ansible | changed: [FG60E -> localhost] => (item=[{'name': '31JFS', 'q_origin_key': '31JFS', 'id': 3, 'group-type': 'firewall', 'authtimeout': 0, 'auth-concurrent-override': 'disable', 'auth-concurrent-value': 0, 'http-digest-realm': '', 'sso-attribute-value': '', 'match': [], 'user-id': 'email', 'password': 'auto-generate', 'user-name': 'disable', 'sponsor': 'optional', 'company': 'optional', 'email': 'enable', 'mobile-phone': 'disable', 'sms-server': 'fortiguard', 'sms-custom-server': '', 'expire-type': 'immediately', 'expire': 14400, 'max-accounts': 0, 'multiple-guest-add': 'disable', 'guest': []}, {'name': 'testuser1', 'q_origin_key': 'testuser1'}])
2022-03-07 21:49:07,422 p=13930 u=marseille n=ansible | TASK [ユーザーリストファイルに新規追加ユーザーを追加] ***************************************************************************************************************************************************************************************
2022-03-07 21:49:07,726 p=13930 u=marseille n=ansible | changed: [FG60E -> localhost]
2022-03-07 21:49:07,738 p=13930 u=marseille n=ansible | TASK [ユーザーリストをもとに設定用playbookを作成] *******************************************************************************************************************************************************************************************
2022-03-07 21:49:08,626 p=13930 u=marseille n=ansible | changed: [FG60E -> localhost]
2022-03-07 21:49:08,642 p=13930 u=marseille n=ansible | TASK [ユーザリストファイルの削除] ***********************************************************************************************************************************************************************************************************
2022-03-07 21:49:09,125 p=13930 u=marseille n=ansible | changed: [FG60E -> localhost]
2022-03-07 21:49:09,134 p=13930 u=marseille n=ansible | TASK [設定用playbookをロード] ***************************************************************************************************************************************************************************************************************
2022-03-07 21:49:09,168 p=13930 u=marseille n=ansible | included: /Users/marseille/Documents/100_Ansible/fortigate_mod_group/playbook/add_user_group.yml for FG60E
2022-03-07 21:49:09,177 p=13930 u=marseille n=ansible | TASK [設定用playbookを実行] *****************************************************************************************************************************************************************************************************************
2022-03-07 21:49:11,054 p=13930 u=marseille n=ansible | changed: [FG60E]
2022-03-07 21:49:11,158 p=13930 u=marseille n=ansible | PLAY RECAP **********************************************************************************************************************************************************************************************************************************
2022-03-07 21:49:11,158 p=13930 u=marseille n=ansible | FG60E                      : ok=9    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
2022-03-07 21:49:11,257 p=13938 u=marseille n=ansible | shutdown complete
```