本記事は Ansible Advent Calendar 2020 の 12/23 の記事です。
Qiitaの投稿は初投稿ですが、大佐のAnsible本欲しさに投稿しました。
#はじめに
Windowsの自作モジュールは作ったけど、libraryで管理し続けるのが辛い・・・
Ansible環境が変わるたびにlibraryにモジュール配置疲れしている人は多いのではないでしょうか?
そんな自作モジュール、コントリビュートしてみませんか?
今回は作成したモジュールを例にWindowsモジュール作成のお作法やPRからマージまでの経緯、Windowsならではのポイントなどについてまとめていきたいと思います。
作成したモジュール
みんなが大好きなこれを無効化したりしなかったりするやつです。
その名もwin_net_adapter_feature。
community.windows v1.2.0でマージされました。
背景
世の中のWindows設定手順書の8割以上がアダプターのIPv6を無効にしているのではないだろうか。。
元々はwin_shellモジュールでGet-xxxxで状態確認して、Set-xxxxで設定を行う
冪等性担保のためにchanged_when: falseやcheck_mode: falseなどを駆使した長いRoleを使ってました。
そのRoleを何度もお世話になる局面が有り、モジュール化したら需要があるのでは?と思いモジュール作成に至りました。
(何故これが今まで放置されていたのかが疑問)
Windowsモジュール開発のお作法
Ansible公式ドキュメントにWindowsモジュール開発のウォークスルーが載ってます。基本的にこちらに従い開発を進めていきます。
ps1ファイルとpyファイル
Windows以外のモジュールに比べ、Windowsモジュールは実際の処理部分を記載するps1ファイルとは別に、exampleやモジュールの説明などのドキュメント文を記載するpyファイルを作成する必要があります。
Windowsモジュールのユーティリティ
Windowsモジュールでは、PowershellのユーティリティとC#のユーティリティを利用することができます。
以下はWindowsモジュールで多く使用されているユーティリティです。
言語 | ユーティリティ名 |
---|---|
Powershell | Ansible.ModuleUtils.Legacy.psm1 |
C# | Ansible.Basic.cs |
ウォークスルーの説明を見ていくと、Ansible.Basic.csはAnsible2.8から出てきた後発のmodule_utils。
どちらを使用しても同じことができるが、C#を使用したほうが簡単に実装できる場合があるみたい。
実際に作成したwin_net_adapter_featureモジュールも、当初Ansible.ModuleUtils.Legacyを使用していましたが、PR後にレビュワーのJborean93さんより、「Ansible.ModuleUtils.LegacyよりAnsible.Basic使ったほうがいいよ」とご指摘があり大幅に修正。
以下にBefore/Afterを貼りますが、C#のmodule_utilsを使用した方が40行も減りました。
おそらくモジュールのパラメータ不足時のエラーハンドリング(Fail-Json)をmodule_utils側で吸収しているのかと思われます。
# WANT_JSON
# POWERSHELL_COMMON
# temporarily disable strictmode, for this module only
#Requires -Module Ansible.ModuleUtils.Legacy
$ErrorActionPreference = "Stop"
$Interface_name = [string[]]
$componentID_name = [string[]]
$params = Parse-Args $args -supports_check_mode $true
$Interface_name = Get-AnsibleParam -obj $params -name "Interface"
$state = Get-AnsibleParam -obj $params -name state -type "str"
$componentID_name = Get-AnsibleParam -obj $params -name "componentID"
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$result = @{
changed = $false
}
If(!$Interface_name) { Fail-Json -message "Absolute Interface is not specified for Interface" }
If(!$state) { Fail-Json -message "Absolute state is not specified for state" }
If(!$componentID_name) { Fail-Json -message "Absolute componentID is not specified for componentID" }
If ($state -eq 'enable'){
$state = $true
}ElseIf ($state -eq 'disable') {
$state = $false
}Else {
Fail-Json -message "Specify the state as 'enable' or 'disable'"
}
If($Interface_name -is [string]) {
If($Interface_name.Length -gt 0) {
$Interfaces = @($Interface_name)
}
Else {
$Interfaces = @()
}
}
Else {
$Interfaces = $Interface_name
}
If($componentID_name -is [string]) {
If($componentID_name.Length -gt 0) {
$componentIDs = @($componentID_name)
}
Else {
$componentIDs = @()
}
}
Else {
$componentIDs = $componentID_name
}
$spec = @{
options = @{
interface = @{ type = 'list'; elements = 'str'; required = $true }
state = @{ type = 'str'; choices = 'disabled', 'enabled'; default = 'enabled' }
component_id = @{ type = 'list'; elements = 'str'; required = $true }
}
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$interface = $module.Params.interface
$state = $module.Params.state
$component_id = $module.Params.component_id
$check_mode = $module.CheckMode
開発したモジュールのテスト
Windowsモジュールではユニットテストは行う必要はありませんが、integrationテストを行う必要があります。Forkしたcommunity.windowsリポジトリの./test/integration/targets/以下にテスト用Playbookを作成します。
テストケースとしては以下のようなものを記載していきます。
モジュールの必須オプションが存在しない場合
今回のmoduleですと、interface名(Ethernet等)が必須のオプションなのですが、interfaceオプションが存在しない場合にfailedで終わるようなテストケース
---
- name: fail when interface isn't set
win_net_adapter_feature:
state: enabled
component_id: ms_tcpip6
register: failed_task
failed_when: not failed_task is failed
パラメータが実機側に存在しない場合
Ethernet10のような実機に存在しないinterface名を指定した場合にfailedで終わるテストケース
- name: fail when interface doesn't exist
win_net_adapter_feature:
interface: Ethernet10
state: enabled
component_id: ms_tcpip6
register: failed_task
failed_when: not failed_task is failed
check modeで実行して、設定が変わったりしてないか
check_modeにもサポートするよう心がけましょう。
check_modeで実行後、win_shellモジュール等でPowershellコマンドレットを使って状態確認し、設定が変わっていないことを確認するテストケースを作ります。
- name: disable multiple interfaces of multiple interfaces - check mode
win_net_adapter_feature:
interface:
- '{{ network_adapter_name }}'
- '{{ network_adapter_name2 }}'
state: disabled
component_id:
- ms_tcpip6
- ms_server
check_mode: yes
register: mutliple_multiple_disable_check
- name: get the status of disable multiple interfaces of multiple interfaces - check mode
ansible.windows.win_shell: |
$info = Get-NetAdapterBinding -Name '{{ network_adapter_name }}', '{{ network_adapter_name2 }}'
@{
'{{ network_adapter_name }}' = @{
ms_tcpip6 = ($info | Where-Object { $_.Name -eq '{{ network_adapter_name }}' -and $_.ComponentID -eq 'ms_tcpip6'}).Enabled
ms_server = ($info | Where-Object { $_.Name -eq '{{ network_adapter_name }}' -and $_.ComponentID -eq 'ms_server'}).Enabled
}
'{{ network_adapter_name2 }}' = @{
ms_tcpip6 = ($info | Where-Object { $_.Name -eq '{{ network_adapter_name2 }}' -and $_.ComponentID -eq 'ms_tcpip6'}).Enabled
ms_server = ($info | Where-Object { $_.Name -eq '{{ network_adapter_name2 }}' -and $_.ComponentID -eq 'ms_server'}).Enabled
}
} | ConvertTo-Json
register: mutliple_multiple_disable_check_actual
- name: assert disable multiple interfaces of multiple interfaces - check mode
assert:
that:
- mutliple_multiple_disable_check is changed
- (mutliple_multiple_disable_check_actual.stdout | from_json)[network_adapter_name]['ms_tcpip6'] == True
- (mutliple_multiple_disable_check_actual.stdout | from_json)[network_adapter_name]['ms_server'] == True
- (mutliple_multiple_disable_check_actual.stdout | from_json)[network_adapter_name2]['ms_tcpip6'] == True
- (mutliple_multiple_disable_check_actual.stdout | from_json)[network_adapter_name2]['ms_server'] == True
最後に
本モジュール作成にあたり、レビュアーのJborean93さんには感謝につきます。
Jborean93さんはWindowsモジュール系のメインコントリビューターであり、ご自身でもたくさんのIssueやモジュールを作成されてます。
今回のPRに関し、様々なご支援をいただきました。
OSSにコントリビュートと聞くとかなりハードルが高いと思われるかもしれませんが、Ansibleは成熟したコミュニティであり、優しいレビュワーの皆様がリードしてくれますので、皆様がお持ちのモジュールをPRしてはどうでしょうか。
余談
当初はOCIとAnsibleネタの投稿を考えていましたが、二日前に@ussvgrさんのAnsible Advent Calendar 12/17の投稿とダダ被りになってしまうことに気づき慌てて方向転換。
とほほ・・余裕持って2,3個ネタは持っておきたい・・・