AnsibleでOSによって挙動を変えるモジュールの実装例について説明します。
モジュールの作成方法については以下のページを参照してください。
OSによって挙動を変えるモジュール
まず、ディストリビューションではなくOSによって挙動を変える実装について説明します。
このモジュール内ではUserクラスとそのサブクラスとして FreeBsdUserクラス、OpenBSDUserクラス、NetBSDUserクラス、SunOSクラス、AIXクラスが定義されています。
サブクラスではcreate_user()、remove_user()、modify_user()メソッドをオーバーライドしています。
Userクラスの__new__メソッドは以下の様な定義になっています。
def __new__(cls, *args, **kwargs):
return load_platform_subclass(User, args, kwargs)
load_platform_subclass関数の実装は以下のようになっています。
def load_platform_subclass(cls, *args, **kwargs):
'''
used by modules like User to have different implementations based on detected platform. See User
module for an example.
'''
this_platform = get_platform()
distribution = get_distribution()
subclass = None
# get the most specific superclass for this platform
if distribution is not None:
for sc in cls.__subclasses__():
if sc.distribution is not None and sc.distribution == distribution and sc.platform == this_platform:
subclass = sc
if subclass is None:
for sc in cls.__subclasses__():
if sc.platform == this_platform and sc.distribution is None:
subclass = sc
if subclass is None:
subclass = cls
return super(cls, subclass).__new__(subclass)
呼び出したクラスのサブクラスのうち、ディストリビューションとプラットフォームが一致する場合はそのサブクラス、ディストリビューションが未定義な場合 (get_distributionがNoneを返す場合) はプラットフォームが一致するサブクラス、一致するものがない場合は呼び出したクラスの__new__を呼ぶようになっています。
あとは、
https://github.com/ansible/ansible/blob/v1.4.1/library/system/user#L1438
user = User(module)
のように呼び出すことで、実行環境のOSに応じたクラスのインスタンスが生成されるというわけです。
ディストリビューションによって挙動を変えるモジュール
ディストリビューションによって挙動を変えるモジュールの例としては、Ansible 1.4から追加されたhostnameモジュールがあります。ソースは https://github.com/ansible/ansible/blob/v1.4.1/library/system/hostname です。
実はhostnameモジュールは私のプルリクエストが取り込まれたものです。
userモジュールのUserクラスと同様に、
Hostnameクラスとそのサブクラスとして DebianHostname、UbuntuHostname、RedHat5Hostname、RedHatHostname、CentOSHostname、FedoraHostname、OpenSUSEHostname、ArchHostnameが定義されています。
ただ、DebianとUbuntuのようにホスト名の設定方法が同じディストリビューションもあるので実装を共通化したいと考えました。
通常の継承ベースの考え方ならHostnameのサブクラスにDebianとUbuntuの共通用のクラスを作りたいところです。が、load_platform_subclassは上に引用したような実装になっていて、子孫クラスではなく直下のサブクラスしか探してくれませんし、サブクラスのget_distribution()の戻り値と比較するのでディストリビューションごとにサブクラスを分ける必要があります。
そこで、ホスト名の設定方法については別クラスにすることにしました。UnimplementedStrategy、GenericStrategy、DebianStrategy、RedHatStrategy、FedoraStrategyが定義されています。
これらのクラスは
https://github.com/ansible/ansible/blob/v1.4.1/library/system/hostname#L170-L178
class DebianHostname(Hostname):
platform = 'Linux'
distribution = 'Debian'
strategy_class = DebianStrategy
class UbuntuHostname(Hostname):
platform = 'Linux'
distribution = 'Ubuntu'
strategy_class = DebianStrategy
のようにHostnameの各サブクラスで利用するものを設定していて、Hostnameクラスの実装で
https://github.com/ansible/ansible/blob/v1.4.1/library/system/hostname#L90-L100
def get_current_hostname(self):
return self.strategy.get_current_hostname()
def set_current_hostname(self, name):
self.strategy.set_current_hostname(name)
def get_permanent_hostname(self):
return self.strategy.get_permanent_hostname()
def set_permanent_hostname(self, name):
self.strategy.set_permanent_hostname(name)
のように呼び出すことによって実装を切り替えています。
汎用的なモジュールを作ったらプルリクエストを送りましょう
AnsibleはPythonで書かれているだけではなく、Pythonの"batteries included"の思想も受け継いでいます。http://www.ansibleworks.com/docs/#modules
Ansible follows a “batteries included” philosophy, so you have a lot of great modules for all manner of IT tasks in the core distribution.
ですので、汎用的なモジュールは喜んで取り込んでもらえます。
2014-05-24追記 サイト更新で上記の記述は無くなっています。Batteries IncludedについてはAnsible for Configuration Managementの最後に書かれています。
Ansible features close to 200 modules in the core distribution, providing a great base to build automation upon. From services and databases to cloud providers, with Ansible you don't have to start from scratch.
https://github.com/ansible/ansible/blob/devel/CONTRIBUTING.md#sharing-a-feature-idea と https://github.com/ansible/ansible/blob/devel/CONTRIBUTING.md#contributing-code-features-or-bugfixes によると、アイディアについてはgithubでチケットを作るかメーリングリストやIRCで相談して、コードをレビューしてもらうにはプルリクエストを送るのがよいです。
hostnameモジュールを作った時も、メーリングリストでMichael DeHaanさんに
https://groups.google.com/d/msg/ansible-project/komqS4WdqHU/cRfWiMnCR3cJ
The right way to get this reviewed would be to submit a pull request to the github project.
と言われてプルリクエストを送りました。当初はDebian, Ubuntu, RHEL6, CentOSぐらいしか対応していませんでしたが、他のディストリビューションでの動作報告を受けて対応ディストリビューションが増えています。
最初は多少不完全でもレビューを受けて改善していけば取り込んでもらえます。
みなさんも汎用的なモジュールを作ったら、ぜひプルリクエストを送りましょう!