規則、ヒント、および落とし穴¶
トピック
モジュールの設計および開発を行う際に、以下の基本的な規則およびヒントに従って、読みやすく使用可能なコードを作成します。
モジュールのスコープ設定¶
特に、自分のモジュールを Ansible Core に組み込みたい場合は、各モジュールに十分な (ただし多すぎない) ロジックと機能が含まれていることを確認してください。これらのガイドラインが難しいと感じたら、「本当にモジュールを書く必要があるのかどうか」を検討してください。
- 各モジュールには、簡潔で十分に機能が定義されている必要があります。基本的には、1 つのことを十分に行うという UNIX の哲学に従ってください。
- 既存のモジュールに
list
やinfo
状態のオプションを追加しないでください。新しい_info
モジュールまたは_facts
モジュールを作成します。 - モジュールは、使用する API またはツールの基礎となるすべてのオプションを把握する必要はありません。たとえば、必要なモジュールパラメーターの安定した値を文書化できないと、モジュールは Ansible Core には組み込まれません。
- モジュールは、リソースと対話するためのロジックの多くを網羅しなければなりません。複雑な API に関する軽量ラッパーにより、ユーザーは Playbook にロジックを過剰にオフロードしなければなりません。Ansible を複雑な API に接続する場合は、API の個々の小さな部分と対話する 複数のモジュールを作成 してください。
- これはコードの重複や相違を招き、統一性や予測不可能性が低くなり、保守が難しくなります。モジュールはビルディングブロックであるべきです。「どのようにしてモジュールに他のモジュールを実行させることができるのか」という質問が浮かんでくる場合は、それがロールを作成する理由になります。
モジュールインターフェースの設計¶
- モジュールがオブジェクトに対処している場合は、可能な限りそのオブジェクトのパラメーターを
name
とするか、またはエイリアスとしてname
を受け入れます。 - ブール値のステータスを受け付けるモジュールは、
yes
、no
、true
、false
などのもの、あるいはユーザーが出力する可能性のあるものは何でも受け付ける必要があります。AnsibleModule の共通コードは、type='bool'
でこれをサポートします。 action
/command
は必須で、宣言的ではありませんが、同じ方法を表示する方法もあります。
一般的なガイドラインおよびヒント¶
- 各モジュールは 1 つのファイルにまとめて自己完結させる必要があり、Ansible で自動転送できるようにします。
- モジュール名は、単語の区切り文字として、ハイフンやスペースの代わりにアンダースコアを使用する必要はあります。ハイフンやスペースを使用すると、Ansible がモジュールをインポートできなくなります。
- モジュールを開発する際には必ず
hacking/test-module.py
スクリプトを使用してください。よくある落とし穴について警告してくれます。 - インストールに固有のファクトを返すローカルモジュールがある場合は、このモジュールに適切な名前が
site_facts
になります。 - 依存関係を排除するか、最小限にします。モジュールに依存関係がある場合は、モジュールファイルの冒頭で文書化し、依存関係のインポートに失敗した場合は JSON エラーメッセージを発生させます。
- ファイルに直接書き込まないようにします。一時ファイルを使用してから、
ansible.module_utils.basic
のatomic_move
関数を使用して、更新された一時ファイルを所定の場所に移動させます。これにより、データの破損を防ぎ、ファイルの正しいコンテキストが保持されるようになります。 - キャッシュを作成しないでください。Ansible は中央のサーバーや権限を持たないように設計されているため、さまざまなパーミッション、オプション、場所を指定して実行しないことを保証することはできません。中央の権限が必要な場合は、それを Ansible の上に置いてください (例: bastion/cm/ci サーバーまたは Tower を使用)。モジュールには組み込まないでください。
- RPM でモジュールをパッケージ化する場合は、
/usr/share/ansible
の制御マシンにモジュールをインストールします。RPM のモジュールのパッケージ化は任意です。
関数およびメソッド¶
- 各関数は簡潔にし、意味のある作業量を記述する必要があります。
- 「Don’t repeat yourself (繰り返さないこと)」は、通常、適している哲学です。
- 関数名にはアンダースコアを使用します (
my_function_name
)。 - 各関数の名前は、それが何をするかを記述する必要があります。
- 各関数にはドキュメント文字列 (docstring) が必要です。
- コードの入れ子を多用しすぎている場合、それは通常、ループ本体が関数であることから利益が得られる可能性のある兆候です。私たちの既存のコードの一部は、時としてこのような例としては最適ではありません。
Python のヒント¶
- URL を取得する場合は、
ansible.module_utils.urls
のfetch_url
またはopen_url
を使用します。urllib2
は使用しない。TLS 証明書をネイティブに検証しないでください。これは https に対して安全ではありません。 - 通常の実行をラップする
main
関数を含めます。 - 条件から
main
機能を呼び出して、ユニットテストにインポートします。以下に例を示します。
if __name__ == '__main__':
main()
モジュール障害の処理¶
モジュールが失敗したときに、何が問題だったのかをユーザーが理解できるようにします。AnsibleModule
の共通 Python コードを使用している場合は、fail_json
を呼び出すと自動的に 失敗した
要素が含まれます。モジュールの失敗動作を適切なものにするために、以下を行います。
msg
に、文字列の説明とともにfailed
のキーを指定します。これを行わないと、Ansible は標準の戻りコードを使用します (ゼロは成功、ゼロ以外はfailure)。- トレースバック (スタックトレース) は発生させません。Ansible はスタックトレースを扱うことができ、解析できないものは自動的に失敗した結果に変換しますが、モジュールの失敗時にスタックトレースを発生させるのはユーザーフレンドリーではありません。
sys.exit()
は使用しないでください。モジュールオブジェクトからfail_json()
を使用してください。
例外 (バグ) を正常に処理¶
- 前もって検証します。早めに失敗し、有用で明確なエラーメッセージを返します。
- 防御的なプログラミングを使用します。モジュールにはシンプルなデザインを使用し、エラーを適切に処理し、直接のスタックトレースを回避します。
- 予測可能な方法で失敗します。失敗がどうしても避けられない場合は、最も期待される方法で失敗します。基礎となるツール、またはシステムの一般的な動作方法を模倣します。
- 実行内容に関する有用なメッセージを表示して、それに例外メッセージを追加します。
- キャッチオール例外は使用しないでください。基になる API で試行されたアクションに関する非常に優れたエラーメッセージがない限り、これらの例外はほとんど役に立ちません。
正確で有益なモジュール出力を作成¶
モジュールは有効な JSON のみを出力しなければなりません。正確で有用なモジュール出力を作成するには、以下のガイドラインに従ってください。
- トップレベルの戻り値の型をハッシュ (ディレクトリー) にします。
- 複雑な戻り値をトップレベルのハッシュ内に入れ子にします。
- トップレベルの戻り値ハッシュ内にリストや単純なスカラー値を組み込みます。
- モジュールの出力を標準エラーに送らないでください。システムが標準エラーと標準出力をマージし、JSON の解析を妨げるためです。
- 標準エラーを捕捉し、標準出力に JSON 形式の変数として返します。これがコマンドモジュールの実装方法です。
- モジュール内で
print("some status message")
は絶対にしないでください。有効な JSON 出力を生成しないためです。 - 変更がない場合でも、有用なデータを常に返します。
- 状態/アクションに影響を及ばさないために、戻り値は一貫したものにしてください (モジュールによっては非常に乱雑なものもあります)。
- 戻り値は再利用可能なものにする。ほとんどの場合は読むことはありませんが、処理して再利用できるものにします。
- diff モードの場合は diff を返す。これは、特定のモジュールでは意味をなさないため、すべてのモジュールに必要なわけではありませんが、該当する場合には使用してください。
- Python の標準的な JSON エンコーダーおよびデコーダーライブラリー を使用して、戻り値を JSON としてシリアライズできるようにします。基本的な python の型 (string、int、dicts、lists など) はシリアライズ可能です。
- exit_json() 経由でオブジェクトを返さないようにしてください。代わりに、オブジェクトから必要なフィールドをディクショナリーのフィールドに変換して返します。
- 多数のホストからの結果が一度に集約されるため、モジュールは関連する出力だけを返すべきです。ログファイルの内容全体を返すのは、一般的には悪い形式です。
モジュールが標準エラー (stderr) を返したり、その他の方法で有効な JSON の生成に失敗した場合でも、実際の出力は Ansible に表示されますが、コマンドは成功しません。
Ansible の規則に準拠¶
Ansible の規則は、すべてのモジュール、Playbook、ロールに渡って予測可能なユーザーインターフェースを提供します。モジュール開発において Ansible の規則に従うには、以下を行います。
- モジュール間で一貫性のある名前を使用します (レガシーとの相違が多数あるため、問題を悪化させないようにしましょう)。
- モジュール内で一貫したパラメーター (引数) を使用します。
- 他のモジュールでパラメーターを正規化します。Ansible とモジュールが接続する API が同じパラメーターに異なる名前を使用している場合は、パラメーターにエイリアスを追加して、ユーザーがタスクや Playbook で使用する名前を選択できるようにします。
- 結果ディクショナリー<common_return_values> の
ansible_facts
フィールドに*_facts
モジュールからファクトを返すことで、他のモジュールからアクセスできるようにします。 - すべての
*_info
モジュールおよび*_facts
モジュールにcheck_mode
を実装します。ファクト情報に基づいて条件付けを行う Playbook は、check_mode
でファクトが返された場合に限り、check_mode
で正しく条件付けを行います。通常はAnsibleModule
のインスタンスを作成する際にsupports_check_mode=True
を追加します。 - モジュール固有の環境変数を使用します。たとえば、
module_utils.urls.fetch_url()
での基本的な認証にmodule_utils.api
のヘルパーを使用していて、デフォルト値を環境変数に依存している場合は、モジュール間の競合を回避するためにAPI_<MODULENAME>_USERNAME
のようなモジュール固有の環境変数を使用します。 - モジュールのオプションはシンプルで焦点を絞ったものにします。既存のオプションに多くの選択肢や状態を読み込んでいる場合は、代わりに新しいシンプルなオプションを追加することを検討してください。
- 可能な場合はオプションのサイズを小さくします。大きなデータ構造をオプションに渡すと、いくつかの作業が省けるかもしれませんが、モジュールに渡す前に簡単に検証できない複雑な要件が追加されてしまいます。
- 複雑なデータをオプションに渡したいのであれば、それを可能にするエキスパートモジュールと、基礎となる API やサービスに対してより「アトミックな」操作を提供するいくつかの小さなモジュールを作成します。複雑な操作には複雑なデータが必要です。その複雑さをタスクやプレイに反映させるか、vars ファイルに反映させるかをユーザが選択できるようにします。
- ユーザーが既存の状態を無視して最終的な状態に集中できるように、(CRUDではなく) 宣言的な操作を実装します。たとえば、
started/stopped
、present/
absent`` を使用します。 - 最終状態が一貫したもの (冪等性) になるようにします。同じシステムに対してモジュールを連続して 2 回実行すると 2 つの異なる状態になってしまう場合は、最終的な状態が一貫しているかどうかを再設計または書き換えてみてください。できない場合には、動作とその理由を記載してください。
- 通常は他のオプションで返されるキーに NA/None が使用されている場合でも、標準の Ansible の戻り値構造内で一貫性のある戻り値を提供します。
- 該当する場合は、モジュールのファミリーに適用される追加のガイドラインに従います。たとえば、AWS モジュールは Amazon のガイドライン に従う必要があります。
モジュールのセキュリティー¶
- シェルからユーザー入力を渡さないようにします。
- 常に戻りコードを確認してください。
subprocess
、Popen
、os.system
ではなく、module.run_command
を常に使用しなければなりません。- 絶対に必要な場合を除き、シェルは使用しないでください。
- シェルを使用しなければならない場合は、
use_unsafe_shell=True
をmodule.run_command
に渡す必要があります。 use_unsafe_shell=True
でモジュール内の変数がユーザー入力から指定される可能性がある場合は、pipes.quote(x)
でラップしなければなりません。- URL を取得する場合は、
ansible.module_utils.urls
のfetch_url
またはopen_url
を使用します。urllib2
は使用しないてください。これは、TLS 証明書をネイティブに検証せず、https に対して安全ではありません。