＜Ansible＞変数を使用した処理のコントロール ~ 文字列編 ~

#はじめに
この記事はAnsibleを勉強し始めた人を対象としたものです。
私自身は、IT未経験で今の会社に入社し、2年目に突入しました。
Ansibleは多少触ったことがある程度ですので、自由自在に使いこなせるよう、日々勉強中です。
#概要
変数（文字列）を定義したファイルを読み取り、その変数を使用して処理をコントロールできることを目的とします。
変数定義ファイルを修正した際、他のファイルの修正を一切必要とせずに処理が正常に行われることを確認します。

具体的な内容ですが、指定した言語を公用語とするG7構成国を、画面上に出力するという処理を実装します。
指定した言語が、どこのG7構成国の公用語か判断する材料となるのが、変数定義ファイルです。
変数定義ファイルには、G7構成国（文字列）と公用語（文字列）を定義します。

今回は、以下3つのパターンを試してみたいと思います。

①varsディレクトリ配下のYMLファイルで変数を定義
②CSVファイルで変数を定義
③国名を入力して処理を開始する（YMLファイルで変数を定義）

変数定義ファイルは、それぞれ次のようになっております。
#####/etc/ansible/roles/international_conference/vars/main.yml（①）
```
---
GX:
  Canada: English
  France: French
  Germany: German
  Italy: Italian
  Japan: Japanese
  UK: English
  USA: English
...
```
#####/etc/ansible/variable_definition.csv（②）
```
IndustrializedNations,CapitalCity,OfficialLanguage
Canada,Ottawa,English
France,Paris,French
Germany,Berlin,German
Italy,Rome,Italian
Japan,Tokyo,Japanese
UK,London,English
USA,D.C.,English
```
#####/etc/ansible/input_vars.yml（③）
```
---
GX:
  - IndustrializedNations: Canada
    CapitalCity: Ottawa
    OfficialLanguage: English
  - IndustrializedNations: France
    CapitalCity: Paris
    OfficialLanguage: French
  - IndustrializedNations: Germany
    CapitalCity: Berlin
    OfficialLanguage: German
  - IndustrializedNations: Italy
    CapitalCity: Rome
    OfficialLanguage: Italian
  - IndustrializedNations: Japan
    CapitalCity: Tokyo
    OfficialLanguage: Japanese
  - IndustrializedNations: UK
    CapitalCity: London
    OfficialLanguage: English
  - IndustrializedNations: USA
    CapitalCity: D.C.
    OfficialLanguage: English
...
```
②と③には、必要な情報だけ抽出できることを示すため、敢えて今回の処理には不要な項目（CapitalCity）も定義しています。

実行サーバはEC2（Amazon Linux）を使用しており、Ansibleのバージョンは2.8.1です。

次の項目から、作成したファイルの中身（タスクの内容）を説明していきます。
#①varsディレクトリ配下のYMLファイルで変数を定義した場合
まず、①のタスクの内容から説明します。
ディレクトリ構成、ファイルの中身は以下の通りです。
##### ディレクトリ構成
```
/etc/ansible
├── site.yml                       # ロールを実行するPlaybook
└── roles                          # ロールを配置
    └── international_conference   # ロール名のディレクトリ
        ├── tasks                  # Tasksセクションを記載したファイルを配置
        │   └── main.yml           # タスク処理を定義
        └── vars                   # Varsセクションを記載したファイルを配置
            └── main.yml           # 変数定義ファイル
```
#####  /etc/ansible/roles/international_conference/tasks/main.yml
```
---
# 公用語の指定
- name: Set variable
  set_fact:
    official_language: English

# 指定した言語が構成国・地域の公用語である場合、メッセージ出力
- name: Display the result
  debug:
    msg: "{{ item.key }}の公用語は{{ item.value }}である"
  with_dict: "{{ GX | default({}) }}"
  when: item.value == "{{ official_language }}"
...
```
はじめのset_factモジュールは、変数を定義するものです。
『official_language』という変数を定義し、値に公用語（上記だと「English」）を指定します。

debugモジュールは、画面上に値を出力します。
item.valueがset_factモジュールで指定した公用語（official_language）と一致すれば、"{{ item.key }}の公用語は{{ item.value }}である" というメッセージが出力されます。
上記の/etc/ansible/roles/international_conference/vars/main.yml（①）で示すとおり、item.keyがG7構成国、item.valueがその国の公用語になります。

ここで、①に対する簡単な説明は以上となります。
ロールを実行する際ですが、Playbook（/etc/ansible/site.yml）のrolesディレクティブでロール名のディレクトリ（international_conference）を指定します。
それでは実際にロールの実行結果を確認してみましょう。

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

TASK [international_conference : Set variable] *************************************************************************************************************************
ok: [localhost]

TASK [international_conference : Display the result] *******************************************************************************************************************
 [WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: item.value == "{{ official_language }}"

ok: [localhost] => (item={'value': u'English', 'key': u'Canada'}) => {
    "msg": "Canadaの公用語はEnglishである"
}
skipping: [localhost] => (item={'value': u'Italian', 'key': u'Italy'})
ok: [localhost] => (item={'value': u'English', 'key': u'USA'}) => {
    "msg": "USAの公用語はEnglishである"
}
skipping: [localhost] => (item={'value': u'French', 'key': u'France'})
skipping: [localhost] => (item={'value': u'German', 'key': u'Germany'})
ok: [localhost] => (item={'value': u'English', 'key': u'UK'}) => {
    "msg": "UKの公用語はEnglishである"
}
skipping: [localhost] => (item={'value': u'Japanese', 'key': u'Japan'})

PLAY RECAP *************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```
公用語に「English」を指定すると、それを公用語とするG7構成国ごとにメッセージが出力されることが確認できます。

ここで、上記結果の「Display the result」を見ると、item の表示される順序がばらばら（/etc/ansible/roles/international_conference/vars/main.yml に書いてある順番ではない）であることが確認できます。
これは、with_dictディレクティブが実行時の順番を考慮しないからです。

今回の記事では、変数を使って処理をコントロールできることを示しています。
変数定義ファイル（/etc/ansible/roles/international_conference/vars/main.yml）を修正した際、他のファイルの修正を一切せず、処理が正常に行われることが望ましいです。
ここで変数定義ファイルを修正し（keyがG20の構成国・地域となるよう、更に13か国分の行数を追加）、どのような結果になるのか確認してみましょう。
#####/etc/ansible/roles/international_conference/vars/main.yml（G20の構成国・地域一覧）
```
---
GX:
  Canada: English
  France: French
  Germany: German
  Italy: Italian
  Japan: Japanese
  UK: English
  USA: English
  EU: plural
  Russia: Russian
  China: Chinese
  India: Hindi
  Brazil: Portuguese
  Mexico: Spanish
  SouthAfrica: plural
  Australia: English
  Korea: Korean
  Indonesia: Indonesian
  SaudiArabia: Arabic
  Turkey: Turkish
  Argentina: Spanish
...
```

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

TASK [international_conference : Set variable] *************************************************************************************************************************
ok: [localhost]

TASK [international_conference : Display the result] *******************************************************************************************************************
 [WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: item.value == "{{ official_language }}"

ok: [localhost] => (item={'value': u'English', 'key': u'Canada'}) => {
    "msg": "Canadaの公用語はEnglishである"
}
skipping: [localhost] => (item={'value': u'Portuguese', 'key': u'Brazil'})
skipping: [localhost] => (item={'value': u'Korean', 'key': u'Korea'})
skipping: [localhost] => (item={'value': u'Italian', 'key': u'Italy'})
skipping: [localhost] => (item={'value': u'Arabic', 'key': u'SaudiArabia'})
ok: [localhost] => (item={'value': u'English', 'key': u'USA'}) => {
    "msg": "USAの公用語はEnglishである"
}
skipping: [localhost] => (item={'value': u'Spanish', 'key': u'Mexico'})
skipping: [localhost] => (item={'value': u'Turkish', 'key': u'Turkey'})
skipping: [localhost] => (item={'value': u'Indonesian', 'key': u'Indonesia'})
skipping: [localhost] => (item={'value': u'Hindi', 'key': u'India'})
skipping: [localhost] => (item={'value': u'French', 'key': u'France'})
skipping: [localhost] => (item={'value': u'plural', 'key': u'SouthAfrica'})
ok: [localhost] => (item={'value': u'English', 'key': u'Australia'}) => {
    "msg": "Australiaの公用語はEnglishである"
}
skipping: [localhost] => (item={'value': u'Chinese', 'key': u'China'})
skipping: [localhost] => (item={'value': u'plural', 'key': u'EU'})
ok: [localhost] => (item={'value': u'English', 'key': u'UK'}) => {
    "msg": "UKの公用語はEnglishである"
}
skipping: [localhost] => (item={'value': u'Japanese', 'key': u'Japan'})
skipping: [localhost] => (item={'value': u'Spanish', 'key': u'Argentina'})
skipping: [localhost] => (item={'value': u'German', 'key': u'Germany'})
skipping: [localhost] => (item={'value': u'Russian', 'key': u'Russia'})

PLAY RECAP *************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```
新たに変数定義ファイル（/etc/ansible/roles/international_conference/vars/main.yml）に追加した分のメッセージ "Australiaの公用語はEnglishである" が表示されています。
これで、変数定義ファイルを修正した際、他ファイルの修正を一切しなくても、処理が正常に行われることを確認できました。
#②CSVファイルで変数を定義した場合
ディレクトリ構成、ファイルの中身は以下の通りです。
##### ディレクトリ構成
```
/etc/ansible
├── site.yml                       # ロールを実行するPlaybook
├── variable_definition.csv        # 変数定義ファイル
└── roles                          # ロールを配置
    └── international_conference   # ロール名のディレクトリ
        ├── tasks                  # Tasksセクションを記載したファイルを配置
        │   └── main.yml           # タスク処理を定義
        └── vars                   # Varsセクションを記載したファイルを配置
            └── main.yml           # 公用語を指定
```
#####  /etc/ansible/roles/international_conference/vars/main.yml
```
---
official_language: English
...
```
#####  /etc/ansible/roles/international_conference/tasks/main.yml
```
---
# 変数定義ファイルの読み込み
- name: Read CSV file
  read_csv:
    path: ./variable_definition.csv
  register: csv_data

# このままメッセージとして出力できないので、一旦変数に格納
- name: Store it in a variable for now
  set_fact:
    industrialized_nations: "{{ '{{' }} csv_data.list | selectattr('OfficialLanguage', '==', '{{ official_language }}') | map(attribute='IndustrializedNations') | list | to_yaml {{ '}}' }}"

# 指定した言語が構成国・地域の公用語である場合、メッセージ出力
- name: Display the result
  debug:
    msg: "{{ industrialized_nations }}の公用語は{{ official_language }}である"
...
```
『official_language』は/etc/ansible/roles/international_conference/vars/main.yml で定義した公用語（English）を指します。

read_csvモジュールは変数定義ファイル（/etc/ansible/variable_definition.csv）を読み込みます。

set_factモジュールでは、条件に当てはまる構成国・地域をリストで格納する変数『industrialized_nations』を定義します。
『selectattr』は、条件に当てはまるものをフィルタリングし、『map』は、"attribute" で指定したものを抽出します。

debugモジュールでは、"{{ industrialized_nations }}の公用語は{{ official_language }}である" というメッセージが出力されます。

それでは実際にロールの実行結果を確認してみましょう。

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

TASK [international_conference : Read CSV file] ************************************************************************************************************************
ok: [localhost]

TASK [international_conference : Store it in a variable for now] *******************************************************************************************************
ok: [localhost]

TASK [international_conference : Display the result] *******************************************************************************************************************
ok: [localhost] => {
    "msg": "[Canada, UK, USA]\nの公用語はEnglishである"
}

PLAY RECAP *************************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```
公用語に「English」を指定すると、それを公用語とするG7の構成国・地域が1つのリストに格納され、メッセージが出力されることが確認できました。

ここで、変数定義ファイル（/etc/ansible/variable_definition.csv）を修正した場合にどのような結果になるのか確認してみましょう。
#####/etc/ansible/variable_definition.csv（G20の構成国・地域一覧）
```
IndustrializedNations,CapitalCity,OfficialLanguage
Canada,Ottawa,English
France,Paris,French
Germany,Berlin,German
Italy,Rome,Italian
Japan,Tokyo,Japanese
UK,London,English
USA,D.C.,English
EU,NoCapital,plural
Russia,Moscow,Russian
China,Beijing,Chinese
India,NewDelhi,Hindi
Brazil,Brasilia,Portuguese
Mexico,MexicoCity,Spanish
SouthAfrica,plural,plural
Australia,Canberra,English
Korea,Seoul,Korean
Indonesia,Jakarta,Indonesian
SaudiArabia,Riyadh,Arabic
Turkey,Ankara,Turkish
Argentina,BuenosAires,Spanish
```

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

TASK [international_conference : Read CSV file] ************************************************************************************************************************
ok: [localhost]

TASK [international_conference : Store it in a variable for now] *******************************************************************************************************
ok: [localhost]

TASK [international_conference : Display the result] *******************************************************************************************************************
ok: [localhost] => {
    "msg": "[Canada, UK, USA, Australia]\nの公用語はEnglishである"
}

PLAY RECAP *************************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```
新たに変数定義ファイル（/etc/ansible/variable_definition.csv）に追加した "Australia" がリストに追加されています。
これで、変数定義ファイルを修正した際、他ファイルの修正を一切しなくても、処理が正常に行われることを確認できました。
#③国名を入力して処理を開始する場合（YMLファイルで変数を定義）
ディレクトリ構成、ファイルの中身は以下の通りです。
##### ディレクトリ構成
```
/etc/ansible
├── hosts             # インベントリ
├── site.yml          # Playbook
└── input_vars.yml    # 変数定義ファイル
```
#####  /etc/ansible/site.yml
```
---
- name: Output the member countries
  hosts: localhost
  gather_facts: False
  vars_files:
    - input_vars.yml
  vars:
    ansible_python_interpreter: /usr/bin/python3
  vars_prompt:
    - name: "Input_information"
      prompt: |
              公用語を入力してください
      private: no
  tasks:
    # 入力した言語を公用語とする構成国・地域をリストに格納する
    - name: Define a variable from the typed characters
      set_fact:
        industrialized_nations: "{{ GX | selectattr('OfficialLanguage', '==', Input_information) | map(attribute='IndustrializedNations') | list | to_yaml }}"

    # 入力した言語が構成国・地域の公用語である場合、メッセージ出力
    - name: Display the result
      debug:
        msg: "{{ industrialized_nations }}の公用語は{{ Input_information }}である"
      when: "{{ industrialized_nations }} != []"
...
```
上記のPlaybookの内容を説明していきます。
まず、『vars_files』で参照するファイル（変数定義ファイル）を指定しています。

『vars_prompt』では、入力した文字列を "Input_information" という変数に格納しています。

『private』ですが、値を "yes" にすることで、入力している文字が表示されません。
パスワードなど、他人に見られたくないものを入力する際に有効になります。

説明は以上です。
それでは、入力した文字列によって異なる出力結果を確認してみましょう。
「English」と入力したときの結果です。

```
PLAY [Output the member countries] *************************************************************************************************************************************

TASK [Define a variable from the typed characters] *********************************************************************************************************************
ok: [localhost]

TASK [Display the result] **********************************************************************************************************************************************
 [WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {{ industrialized_nations }} != []

ok: [localhost] => {
    "msg": "[Canada, UK, USA]\nの公用語はEnglishである"
}

PLAY RECAP *************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```
"[Canada, UK, USA]\nの公用語はEnglishである" というメッセージが出力されることを確認できます。

ここで、変数定義ファイル（/etc/ansible/input_vars.yml）を修正した場合にどのような結果になるのか確認してみましょう。
#####/etc/ansible/input_vars.yml（G20の構成国・地域一覧）
```
---
GX:
  - IndustrializedNations: Canada
    CapitalCity: Ottawa
    OfficialLanguage: English
  - IndustrializedNations: France
    CapitalCity: Paris
    OfficialLanguage: French
  - IndustrializedNations: Germany
    CapitalCity: Berlin
    OfficialLanguage: German
  - IndustrializedNations: Italy
    CapitalCity: Rome
    OfficialLanguage: Italian
  - IndustrializedNations: Japan
    CapitalCity: Tokyo
    OfficialLanguage: Japanese
  - IndustrializedNations: UK
    CapitalCity: London
    OfficialLanguage: English
  - IndustrializedNations: USA
    CapitalCity: D.C.
    OfficialLanguage: English
  - IndustrializedNations: EU
    CapitalCity: NoCapital
    OfficialLanguage: plural
  - IndustrializedNations: Russia
    CapitalCity: Moscow
    OfficialLanguage: Russian
  - IndustrializedNations: China
    CapitalCity: Beijing
    OfficialLanguage: Chinese
  - IndustrializedNations: India
    CapitalCity: NewDelhi
    OfficialLanguage: Hindi
  - IndustrializedNations: Brazil
    CapitalCity: Brasilia
    OfficialLanguage: Portuguese
  - IndustrializedNations: Mexico
    CapitalCity: MexicoCity
    OfficialLanguage: Spanish
  - IndustrializedNations: SouthAfrica
    CapitalCity: plural
    OfficialLanguage: plural
  - IndustrializedNations: Australia
    CapitalCity: Canberra
    OfficialLanguage: English
  - IndustrializedNations: Korea
    CapitalCity: Seoul
    OfficialLanguage: Korean
  - IndustrializedNations: Indonesia
    CapitalCity: Jakarta
    OfficialLanguage: Indonesian
  - IndustrializedNations: SaudiArabia
    CapitalCity: Riyadh
    OfficialLanguage: Arabic
  - IndustrializedNations: Turkey
    CapitalCity: Ankara
    OfficialLanguage: Turkish
  - IndustrializedNations: Argentina
    CapitalCity: BuenosAires
    OfficialLanguage: Spanish
...
```

```
PLAY [Output the member countries] *************************************************************************************************************************************

TASK [Define a variable from the typed characters] *********************************************************************************************************************
ok: [localhost]

TASK [Display the result] **********************************************************************************************************************************************
 [WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {{ industrialized_nations }} != []

ok: [localhost] => {
    "msg": "[Canada, UK, USA, Australia]\nの公用語はEnglishである"
}

PLAY RECAP *************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```
新たに変数定義ファイル（/etc/ansible/input_vars.yml）に追加した "Australia" がリストに追加されています。
これで、変数定義ファイルを修正した際、他ファイルの修正を一切しなくても、処理が正常に行われることを確認できました。
# あとがき
知りたい情報は散らばっていましたが、自分なりにまとめることで情報を整理でき、理解を深めることができました。
次回は同じタイトルで、変数に数値を定義したロール、Playbookを作成して検証してみたいと思います。