4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Ansible Advent Calendar 2020

Day 23

コントリビュートcommunity.windows~モジュール作成編

Last updated at Posted at 2020-12-22

本記事は Ansible Advent Calendar 2020 の 12/23 の記事です。
Qiitaの投稿は初投稿ですが、大佐のAnsible本欲しさに投稿しました。

#はじめに
Windowsの自作モジュールは作ったけど、libraryで管理し続けるのが辛い・・・
Ansible環境が変わるたびにlibraryにモジュール配置疲れしている人は多いのではないでしょうか?
そんな自作モジュール、コントリビュートしてみませんか?
今回は作成したモジュールを例にWindowsモジュール作成のお作法やPRからマージまでの経緯、Windowsならではのポイントなどについてまとめていきたいと思います。

作成したモジュール

みんなが大好きなこれを無効化したりしなかったりするやつです。
001.PNG
その名も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側で吸収しているのかと思われます。

Ansible.ModuleUtils.Legacy.psm1を使った場合のparam定義部分
# 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
}
Ansible.Basic.AnsibleModule.csを使ったparam定義部分
$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個ネタは持っておきたい・・・

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?