Ansible2.6でAWS KMSを使うときにハマったこと

# はじめに
AnsibleでAWS KMS（以下KMS）を構築＆利用しようとして、Ansibleの使用言語であるPythonにハマった話です。
インフラ自動構築ツールとしてのAnsibleの立ち位置や、使い方などは省略させてもらいます。
（Ansible歴が15営業日程度なので、アレレな使い方をしている可能性があります。）
# 前提条件
### Ansibleでしたかったこと
1. [KMSカスタマーマスターキー](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html)の構築（以下CMK）
2. CMKにエイリアスを付与する
3. CMKをエイリアスから取得する
3. CMKでEBSを暗号化

### ハマった経緯
[AnsibleのAWSモジュール一覧](https://docs.ansible.com/ansible/2.6/modules/list_of_cloud_modules.html)を見たところ、KMS構築モジュールがないやんけ！
（え、あるよと思った方、実はこの[aws_kmsモジュール](https://docs.ansible.com/ansible/2.6/modules/aws_kms_module.html#aws-kms-module)はKMSを利用するIAMロール・ユーザーを定める代物です。）
となり、Ansibleから[AWS CloudFormation](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html)（以下CloudFormation）を呼び（[使用モジュール](https://docs.ansible.com/ansible/2.6/modules/cloudformation_module.html#cloudformation-module)）、Ansibleでしたかったこと1 & 2を実施するという荒技をしました。
[aws_kms_factsモジュール](https://docs.ansible.com/ansible/2.6/modules/aws_kms_facts_module.html#aws-kms-facts-module)（以下aws_kms_facts）で暗号化に使いたいCMKをエイリアスのフィルターで取得し、[ec2_volモジュール](https://docs.ansible.com/ansible/2.6/modules/ec2_vol_module.html#ec2-vol-module)で暗号化しようとしたときに、ことは起こりました。
# 実行Ansibleソース
#### エイリアスからEBS暗号化に使用するCMKを取得する
```yml
- name: KMS for EBS
  aws_kms_facts:
    region: "{{ vars.region }}"
    filters:
      alias: "{{ vars.kms_by_cloudformation.kms_ebs_tag }}"
  register: _aws_kms_facts_for_ebs
- debug: var=_aws_kms_facts_for_ebs
- debug: var=_aws_kms_facts_for_ebs.keys
```
#### エイリアスからEBS暗号化に使用するCMKを取得した結果の標準出力
`_aws_kms_facts_for_ebs.keys[0].key_id` を取得したかったのです。
なのに、`_aws_kms_facts_for_ebs.keys`がないだと。。
参照：https://qiita.com/odashun1015/items/f7bd23336264f022e3a1

```json
ok: [localhost] => {
    "_aws_kms_facts_for_ebs": {
        "changed": false, 
        "failed": false, 
        "keys": [
            {
                "aliases": [
                    "test-kms-ebs"
                ], 
                "aws_account_id": "123456789", 
                "creation_date": "2018-12-21T03:05:33.885000+00:00", 
                "description": "customer master key for ebs", 
                "enabled": true, 
                "grants": [], 
                "key_arn": "arn:aws:kms:eu-west-1:123456789:key/123456789abc-123456789-abc", 
                "key_id": "123456789abc-123456789-abc", 
                "key_manager": "CUSTOMER", 
                "key_state": "Enabled", 
                "key_usage": "ENCRYPT_DECRYPT", 
                "origin": "AWS_KMS", 
                "policies": [
                    "{\n  \"Version\" : \"2012-10-17\",\n  \"Id\" : \"kms-ebs\",\n  \"Statement\" : [ {\n    \"Sid\" : \"Enable IAM User Permissions\",\n    \"Effect\" : \"Allow\",\n    \"Principal\" : {\n      \"AWS\" : \"arn:aws:iam::123456789:root\"\n    },\n    \"Action\" : \"kms:*\",\n    \"Resource\" : \"*\"\n  }, {\n    \"Sid\" : \"Allow access for Key Administrators\",\n    \"Effect\" : \"Allow\",\n    \"Principal\" : {\n      \"AWS\" : \"arn:aws:iam::123456789:user/TestUser\"\n    },\n    \"Action\" : [ \"kms:Create*\", \"kms:Describe*\", \"kms:Enable*\", \"kms:List*\", \"kms:Put*\", \"kms:Update*\", \"kms:Revoke*\", \"kms:Disable*\", \"kms:Get*\", \"kms:Delete*\", \"kms:TagResource\", \"kms:UntagResource\", \"kms:ScheduleKeyDeletion\", \"kms:CancelKeyDeletion\" ],\n    \"Resource\" : \"*\"\n  } ]\n}"
                ], 
                "tags": {
                    "Name": "test-kms-ebs"
                }
            }
        ]
    }
}
ok: [localhost] => {
    "_aws_kms_facts_for_ebs.keys": "<built-in method keys of dict object at 0x2715490>"
}
```
#### EBS暗号化⇨エラー発生
```yml
- name: Encrypted EBS for App Server 1
  ec2_vol:
    region: "{{ vars.region }}"
    device_name: "{{ vars.ec2_app.volumes.volume_02.device_name }}"
    volume_type: "{{ vars.ec2_app.volumes.volume_02.volume_type }}"
    volume_size: "{{ vars.ec2_app.volumes.volume_02.volume_size }}"
    delete_on_termination: "{{ vars.ec2_app.volumes.volume_02.delete_on_termination }}"
    encrypted: yes
    kms_key_id: "{{ _aws_kms_facts_for_ebs.keys[0].key_id }}"
    tags:
      Name: "{{ vars.ec2_app_01.instance_tags.Name }}"
  register: _ec2_vol_app_01_enc
- debug: var=_ec2_vol_app_01_enc
```
# 出現したエラー
```json
TASK [ec2 : Encrypted EBS for App Server 1] ******************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: dict object has no element 0\n\nThe error appears to have been in '/home/test/ansible/roles/ec2/tasks/main.yml': line 67, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: Encrypted EBS for App Server 1\n  ^ here\n"}
	to retry, use: --limit @/home/test/ansible/site.retry

[localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. 
```

え、aws_kms_facts実行結果で取得できるdictオブジェクトに確かに「keys」というキーはあるよ！
なぜバリューが取れない？？。上記参照ページに出会うまで、キー名を変えたりして、方向性の違うことをしてました。

# 解決策
EBS暗号化を、aws_kms_facts実行結果で取得できるdictオブジェクトのキーをひとつひとつ舐め、「keys」というキーがあれば、そのバリューを取得する処理に変更しました。
EBS暗号化ソースは以下です。

```yml
- name: Encrypted EBS for App Server 1
  ec2_vol:
    region: "{{ vars.region }}"
    device_name: "{{ vars.ec2_app.volumes.volume_02.device_name }}"
    volume_type: "{{ vars.ec2_app.volumes.volume_02.volume_type }}"
    volume_size: "{{ vars.ec2_app.volumes.volume_02.volume_size }}"
    delete_on_termination: "{{ vars.ec2_app.volumes.volume_02.delete_on_termination }}"
    encrypted: yes
    kms_key_id: >-
      {% if item.key == 'keys' %}{{ item.value[0].key_id }}{% endif %}
    instance: "{{ _ec2_app_01.tagged_instances[0].id }}"
    tags:
      Name: "{{ vars.ec2_app_01.instance_tags.Name }}"
  with_dict: "{{ _aws_kms_facts_for_ebs }}"
  register: _ec2_vol_app_01_enc
- debug: var=_ec2_vol_app_01_enc
```
# まとめ
Pythonのkeysは特別な意味を持つので、オブジェクト内の「keys」キーは、[keys()](https://note.nkmk.me/python-dict-keys-values-items/)と間違われないようにしましょう