aws向けAnsible自作モジュールを作る

# 概要

 AWS向けのAnsible自作モジュールを公式のAWSモジュールに見習って作り方を学びたいと思います
 ちなみに公式のAWSモジュールのソースコードは[こちら](https://github.com/ansible/ansible/tree/devel/lib/ansible/modules/cloud/amazon)で確認できます

自作したモジュールの置き場はplaybookと同じディレクトリ内にlibraryディレクトリに配置すれば自動的に読み込まれます
それ以外は環境変数`ANSIBLE_LIBRARY`、コマンドパラメータ`--module-path`、ansible.cfgの`library`で指定することができます

```
playbook.yml
library/
  |-----my_lib_hoge.yml
  |-----my_lib_fuga.yml
```

# さっそくコード

以下ではアプリケーションロードバランサーの情報を取得するコードになります
細かい説明はコード内のコメントを参照ください

```python:my_describe_elbv2.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

DOCUMENTATION = '''
module: qiita sample module
short_description: describe elb v2 by elb name
description:
    - Gather facts about EC2 Elastic Load Balancers V2 in AWS
version_added: "2.4"
author:
    - "comefigo"
options:
  names:
    description:
      - List of ELB V2 names
    required: True
    default: null
'''

# Ansibleモジュールのおまじない
from ansible.module_utils.basic import AnsibleModule

# awsとのやりとりに便利な機能を提供してくれるutility
from ansible.module_utils.ec2 import (
    boto3_conn,
    boto_exception,
    ec2_argument_spec,
    get_aws_connection_info,
    snake_dict_to_camel_dict,
    camel_dict_to_snake_dict
)

def main():
    # aws_access_key, aws_secret_key, ec2_url などのパラメータ定義が取得できる
    fields = ec2_argument_spec()

    # 取得したパラメータにnamesというパラメータ定義を追加します(dict型)
    fields.update(dict(
        names={'required': True, 'type': 'list'}
    ))

    # このモジュールで必要なパラメータを引数にmoduleを生成
    module = AnsibleModule(argument_spec=fields)

    result = {}

    try:
        # aws接続用に必要なパラメータを取得
        region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True)

        # 上記で取得した値でboto3クライアントを生成
        elbv2 = boto3_conn(module, conn_type='client', resource='elbv2', region=region, endpoint=ec2_url, **aws_connect_kwargs)

        ############################################################
        #  boto3のクライアントを取得できたのであとは好きなように実装してください
        #
        # describe ELB Application Load Balancer
        ret = elbv2.describe_load_balancers(Names=module.params['names'])

        # 結果を追加
        # キャメルケースからスネークケースに変換
        result.update(camel_dict_to_snake_dict(ret))

    except Exception as err:
        import traceback
        error_msg = boto_exception(err)

        # 異常時はmodule.fail_jsonに戻り値を出力
        module.fail_json(msg=error_msg, exception=traceback.format_exc())

    # 正常時はmodule.exit_jsonに戻り値を出力
    module.exit_json(**result)

if __name__ == '__main__':
    main()
```

## 使い方

ファイル名がそのままモジュール名になります

```
- my_describe_elbv2:
    aws_access_key: xxxxxxxxxx
    aws_secret_key: xxxxxxxxxx
    ec2_url: yyyyyyy
    names:
      - my-elb1
      - my-elb2
```

## ポイント

[module_utils.ec2](https://github.com/ansible-collections/amazon.aws/blob/master/plugins/module_utils/ec2.py)で提供されているAWS接続用の各種機能を使うことで簡単に実装できます
boto3のクライアントが生成された後はboto3に従って実装できますので、好き勝手に作れます

## ec2_argument_spec

AWS関連のモジュールで必要な`aws_access_key`、`aws_secret_key`、`security_token`、`ec2_url`、`profile`、`validate_certs`、~~`region`~~ のモジュール用のパラメータ定義が取得できる

## boto3_conn

boto3のコネクションオブジェクトが取得できる

## snake_dict_to_camel_dict、camel_dict_to_snake_dict

AWSからのJSONレスポンスのキー値はキャメルケースになっているため、pythonのコード規約に合わせてスネークケースに変換する時に`camel_dict_to_snake_dict`を使用します
また、その逆の時は`snake_dict_to_camel_dict`を使用します

## required_if（サンプルコードでは未使用）

特定の条件下でパラメータを必須とする時はrequired_ifで指定します
例) stateがpresentの時にarg1とarg2を必須とする

```
module = AnsibleModule(argument_spec=fields,
                       required_if=[('state', 'present', ['arg1', 'arg2'])])
```

## required_together（サンプルコードでは未使用）

特定のパラメータを使用する時に関連するパラメータを必須指定しなければならない時にrequired_togetherを指定します
例) arg1とarg2とarg3はセットで指定しなければならない
 
```
module = AnsibleModule(argument_spec=fields,
                       required_together=[['arg1', 'arg2', 'arg3']])
```

## リトライを実装しましょう！

上記のコードにはないですが、**リトライ処理は必ず実装しましょう！**
理由としては、AWSのみならず共通して言えるのがAPIをコールしてなにかする系のモジュールでは、コール先がコール制限（1秒間にコールできる数）やネットワークの品質によるエラーになることが割とあります。そのたびに処理が中断されるのもつらいので、冪等性を担保しつつリトライ処理を実装しましょう！（※リトライ処理した分だけリソースが追加されないように）


いかがでしょうか
かなり簡単に実装できたのではないでしょうか？

Enjoy Asinble!!