Windows 模块开发演练

在本节中,我们将逐步介绍如何开发、测试和调试 Ansible Windows 模块。

由于 Windows 模块是用 Powershell 编写的,并且需要在 Windows 主机上运行,因此本指南与通常的开发演练指南有所不同。

本节涵盖的内容

Windows 环境设置

与可以在运行 Ansible 的主机上运行的 Python 模块开发不同,Windows 模块需要为 Windows 主机编写和测试。虽然可以从 Microsoft 下载 Windows 的评估版,但这些映像通常未经进一步修改就无法被 Ansible 使用。设置 Windows 主机以便 Ansible 可以使用的最简单方法是使用 Vagrant 设置虚拟机。Vagrant 可用于下载称为boxes的现有操作系统映像,然后将其部署到 VirtualBox 等虚拟机管理程序。这些 boxes 可以在离线创建和存储,也可以从名为 Vagrant Cloud 的中央存储库下载。

本指南将使用 packer-windoze 存储库创建的 Vagrant boxes,这些 boxes 也已上传到 Vagrant Cloud。要了解有关如何创建这些映像的更多信息,请转到 GitHub 存储库并查看 README 文件。

在开始之前,必须安装以下程序(请参阅 Vagrant 和 VirtualBox 文档以获取安装说明)

  • Vagrant

  • VirtualBox

在虚拟机中创建一个 Windows 服务器

要创建单个 Windows Server 2016 实例,请运行以下命令

vagrant init jborean93/WindowsServer2016
vagrant up

这将从 Vagrant Cloud 下载 Vagrant box 并将其添加到主机上的本地 boxes,然后在 VirtualBox 中启动该实例。首次启动时,Windows 虚拟机将运行 sysprep 过程,然后自动创建 HTTP 和 HTTPS WinRM 侦听器。一旦侦听器在线,Vagrant 将完成其过程,之后 Ansible 可以使用该虚拟机。

创建一个 Ansible 清单

以下 Ansible 清单文件可用于连接到新创建的 Windows 虚拟机

[windows]
WindowsServer  ansible_host=127.0.0.1

[windows:vars]
ansible_user=vagrant
ansible_password=vagrant
ansible_port=55986
ansible_connection=winrm
ansible_winrm_transport=ntlm
ansible_winrm_server_cert_validation=ignore

注意

端口 55986 由 Vagrant 自动转发到创建的 Windows 主机,如果这与现有的本地端口冲突,则 Vagrant 将自动使用另一个随机端口并在输出中显示。

创建的操作系统基于映像集。可以使用以下映像

当主机在线时,可以通过 RDP 在 127.0.0.1:3389 上访问,但如果存在冲突,则端口可能不同。要删除主机,请运行 vagrant destroy --force,Vagrant 将自动删除该虚拟机和与该虚拟机关联的任何其他文件。

虽然这在单个 Windows 实例上测试模块时很有用,但这些主机如果没有修改,就无法与基于域的模块一起使用。ansible-windows 的 Vagrantfile 可用于创建要在 Ansible 中使用的测试域环境。此存储库包含三个文件,Ansible 和 Vagrant 都使用这些文件在域环境中创建多个 Windows 主机。这些文件是

  • Vagrantfile:Vagrant 文件,用于读取 inventory.yml 的清单设置并配置所需的主机

  • inventory.yml:包含所需的主机和其他连接信息,例如 IP 地址和转发的端口

  • main.yml:Vagrant 调用的 Ansible Playbook,用于配置域控制节点并将子主机加入域

默认情况下,这些文件将创建以下环境

  • 在 Windows Server 2016 上运行的单个 AD 域控制器

  • 每个主要的 Windows Server 版本都加入该域的五个子主机

  • DNS 名称为 domain.local 的域

  • 每个主机上的本地管理员帐户,用户名是 vagrant,密码是 vagrant

  • 密码为 VagrantPass1 的域管理员帐户 [email protected]

如果需要,可以通过更改 inventory.yml 文件中的变量 domain_* 来修改域名和帐户。还可以通过更改在 domain_children 键下定义的主机来修改清单文件,以配置更多或更少的服务器。主机变量 ansible_host 是将分配给 VirtualBox 仅主机网络适配器的专用 IP,而 vagrant_box 是将用于创建虚拟机的 box。

配置环境

要按原样配置环境,请运行以下命令

git clone https://github.com/jborean93/ansible-windows.git
cd vagrant
vagrant up

注意

Vagrant 按顺序配置每个主机,因此这可能需要一些时间才能完成。如果在设置域的 Ansible 阶段发生任何错误,请运行 vagrant provision 以重新运行该步骤。

与使用 Vagrant 设置单个 Windows 实例不同,这些主机也可以直接使用 IP 地址以及通过转发的端口访问。通过仅主机网络适配器访问它更容易,因为使用了正常的协议端口,例如,RDP 仍然通过 3389。如果无法使用仅主机网络 IP 解析主机,则可以使用以下转发的端口通过 127.0.0.1 访问以下协议

  • RDP: 295xx

  • SSH: 296xx

  • WinRM HTTP: 297xx

  • WinRM HTTPS: 298xx

  • SMB: 299xx

xx 替换为清单文件中域控制器的条目编号,其中域控制器以 00 开始,并从那里递增。例如,在默认的 inventory.yml 文件中,SERVER2012R2 的 WinRM over HTTPS 通过端口 29804 转发,因为它是 domain_children 中的第四个条目。

Windows 新模块开发

创建新模块时,需要注意以下几点

  • 模块代码位于 PowerShell (.ps1) 文件中,而文档则包含在同名的 Python (.py) 文件中

  • 避免在模块中使用 Write-Host/Debug/Verbose/Error,并将需要返回的内容添加到 $module.Result 变量中

  • 要使模块失败,请调用 $module.FailJson("failure message here"),可以将异常或 ErrorRecord 设置为第二个参数,以获得更详细的错误消息

  • 您可以将异常或 ErrorRecord 作为第二个参数传递给 FailJson("failure", $_),以获得更详细的输出

  • 大多数新模块在合并到 Ansible 主代码库之前,都需要进行检查模式和集成测试

  • 避免在大型代码块上使用 try/catch 语句,而是将其用于单个调用,以便错误消息可以更具描述性

  • 在使用 try/catch 语句时,尽量捕获特定的异常

  • 除非必要,否则避免使用 PSCustomObject

  • ./lib/ansible/module_utils/powershell/ 中查找常用函数,并使用那里的代码,而不是重复工作。可以通过添加行 #Requires -Module * 来导入它们,其中 * 是要导入的文件名,并且在使用 Ansible 运行时,它将自动包含在发送到 Windows 目标的模块代码中

  • 除了 PowerShell 模块实用程序外,C# 模块实用程序也存储在 ./lib/ansible/module_utils/csharp/ 中,如果存在 #AnsibleRequires -CSharpUtil * 行,则会在模块执行中自动导入它们

  • C# 和 PowerShell 模块实用程序实现相同的目标,但 C# 允许开发人员实现底层任务,例如调用 Win32 API,并且在某些情况下速度更快

  • 确保代码在 Windows Server 2016 及更高版本上的 Powershell v5.1 及更高版本下运行;如果需要更高的最低 Powershell 或操作系统版本,请确保文档中明确反映这一点

  • Ansible 在严格模式版本 2.0 下运行模块。请务必通过在您的开发脚本顶部添加 Set-StrictMode -Version 2.0 来启用该模式进行测试

  • 如果可能,优先使用本机 Powershell cmdlet,而不是可执行文件调用

  • 使用完整的 cmdlet 名称而不是别名,例如使用 Remove-Item 而不是 rm

  • 使用带有 cmdlet 的命名参数,例如使用 Remove-Item -Path C:\temp 而不是 Remove-Item C:\temp

一个非常基础的 Powershell 模块 win_environment 结合了 Powershell 模块的最佳实践。它演示了如何实现检查模式和差异支持,并且还显示了在满足特定条件时向用户发出的警告。

稍微高级一点的模块是 win_uri,它还演示了如何使用不同的参数类型(bool、str、int、list、dict、path)和参数的选择,如何使模块失败以及如何处理异常。

作为新的 AnsibleModule 包装器的一部分,输入参数基于参数规范定义和验证。可以在参数规范的根级别设置以下选项

  • mutually_exclusive: 一个列表的列表,其中内部列表包含不能一起设置的模块选项

  • no_log: 阻止模块向 Windows 事件日志发出任何日志

  • options: 一个字典,其中键是模块选项,值是该选项的规范

  • required_by: 一个字典,如果设置了键指定的选项,则必须设置值指定的选项

  • required_if: 一个列表的列表,其中内部列表包含 3 个或 4 个元素;
    • 第一个元素是要检查其值的模块选项

    • 第二个元素是由第一个元素指定的选项的值,如果匹配,则运行 required if 检查

    • 第三个元素是匹配上述情况时所需的模块选项列表

    • 可选的第四个元素是一个布尔值,用于说明是否需要第三个元素中的所有模块选项(默认值:$false)或仅需要一个($true

  • required_one_of: 一个列表的列表,其中内部列表包含必须设置至少一个的模块选项

  • required_together: 一个列表的列表,其中内部列表包含必须一起设置的模块选项

  • supports_check_mode: 模块是否支持检查模式,默认情况下为 $false

模块的实际输入选项在 options 值中设置为字典。此字典的键是模块选项名称,而值是该模块选项的规范。每个规范都可以设置以下选项

  • aliases: 模块选项的别名列表

  • choices: 模块选项的有效值列表,如果 type=list,则每个列表值都将针对 choices 进行验证,而不是列表本身

  • default: 如果未设置,则为模块选项的默认值

  • deprecated_aliases: 一个哈希表列表,用于定义已弃用的别名以及将在其中删除它们的版本。每个条目必须包含键 namecollection_name 以及 versiondate

  • elements: 当 type=list 时,这将设置每个列表值的类型,这些值与 type 相同

  • no_log: 将在 module_invocation 返回值中返回之前对输入值进行清理

  • removed_in_version: 说明何时删除已弃用的模块选项,如果设置了该选项,则会向最终用户显示警告

  • removed_at_date: 说明删除已弃用的模块选项的日期 (YYYY-MM-DD),如果设置了该选项,则会向最终用户显示警告

  • removed_from_collection: 说明将从哪个集合中删除已弃用的模块选项;如果指定了 removed_in_versionremoved_at_date 之一,则必须指定

  • required: 当未设置模块选项时将失败

  • type: 模块选项的类型,如果未设置,则默认为 str。有效类型为;
    • bool: 一个布尔值

    • dict: 一个字典值,如果输入是 JSON 或 key=value 字符串,则会将其转换为字典

    • float: 一个浮点数或 Single

    • int: 一个 Int32 值

    • json: 如果输入是字典,则该字符串值将转换为 JSON 字符串

    • list: 一个值列表,如果设置了 elements=<type>,则可以转换单个列表值类型。如果 elements=dict,则定义 options,这些值将针对参数规范进行验证。当输入为字符串时,字符串将按 , 分割,并修剪任何空格

    • path: 一个字符串,其中诸如 %TEMP% 之类的值将根据环境变量值进行扩展。如果输入值以 \\?\ 开头,则不会运行扩展

    • raw: Ansible 传入的值不进行任何转换

    • sid: 将 Windows 安全标识符值或 Windows 帐户名称转换为 SecurityIdentifier

    • str: 该值将转换为字符串

type=dict,或 type=list 并且 elements=dict 时,也可以为该模块选项设置以下键

  • apply_defaults: 如果为 True,则该值基于该键的 options 规范默认值;如果为 False,则该值为 null。仅当模块选项未由用户定义且 type=dict 时有效。

  • mutually_exclusive: 与根级别的 mutually_exclusive 相同,但针对子字典中的值进行验证。

  • options: 与根级别的 options 相同,但包含子选项的有效选项。

  • required_if: 与根级别的 required_if 相同,但针对子字典中的值进行验证。

  • required_by: 与根级别的 required_by 相同,但针对子字典中的值进行验证。

  • required_together: 与根级别的 required_together 相同,但针对子字典中的值进行验证。

  • required_one_of: 与根级别的 required_one_of 相同,但针对子字典中的值进行验证。

模块类型也可以是将值转换为模块选项所需值的委托函数。例如,以下代码片段显示了如何创建一个创建 UInt64 值的自定义类型。

$spec = @{
    uint64_type = @{ type = [Func[[Object], [UInt64]]]{ [System.UInt64]::Parse($args[0]) } }
}
$uint64_type = $module.Params.uint64_type

如有疑问,请查看其他一些核心模块,了解它们的实现方式。

有时 Windows 提供多种方式来完成任务;以下是编写模块时应优先考虑的顺序:

  • 原生 PowerShell cmdlet,例如 Remove-Item -Path C:\temp -Recurse

  • .NET 类,例如 [System.IO.Path]::GetRandomFileName()

  • 通过 New-CimInstance cmdlet 使用 WMI 对象

  • 通过 New-Object -ComObject cmdlet 使用 COM 对象

  • 调用本机可执行文件,例如 Secedit.exe

PowerShell 模块支持内置于 PowerShell 的 #Requires 选项的一小部分子集,以及 #AnsibleRequires 指定的一些 Ansible 特定的要求。这些语句可以放置在脚本中的任何位置,但最常见的是在顶部附近。它们用于更容易地声明模块的要求,而无需编写任何检查。每个 requires 语句必须单独占一行,但一个脚本中可以有多个 requires 语句。

以下是在 Ansible 模块中可以使用的检查:

  • #Requires -Module Ansible.ModuleUtils.<module_util>: 在 Ansible 2.4 中添加,指定要为模块执行加载的 module_util。

  • #Requires -Version x.y: 在 Ansible 2.5 中添加,指定模块所需的 PowerShell 版本。如果不满足此要求,模块将失败。

  • #AnsibleRequires -PowerShell <module_util>: 在 Ansible 2.8 中添加,类似于 #Requires -Module,这指定要为模块执行加载的 module_util。

  • #AnsibleRequires -CSharpUtil <module_util>: 在 Ansible 2.8 中添加,指定要为模块执行加载的 C# module_util。

  • #AnsibleRequires -OSVersion x.y: 在 Ansible 2.5 中添加,指定模块所需的操作系统构建版本,如果不满足此要求,则会失败。实际操作系统版本是从 [Environment]::OSVersion.Version 获取的。

  • #AnsibleRequires -Become: 在 Ansible 2.5 中添加,强制执行运行器以 become 运行模块,这主要用于绕过 WinRM 限制。如果未指定 ansible_become_user,则改用 SYSTEM 帐户。

#AnsibleRequires -PowerShell#AnsibleRequires -CSharpUtil 支持更多功能,例如:

  • 导入集合中包含的 util(在 Ansible 2.9 中添加)

  • 通过相对名称导入 util(在 Ansible 2.10 中添加)

  • 通过将 -Optional 添加到导入声明来指定 util 是可选的(在 Ansible 2.12 中添加)。

有关更多详细信息,请参见以下示例

# Imports the PowerShell Ansible.ModuleUtils.Legacy provided by Ansible itself
#AnsibleRequires -PowerShell Ansible.ModuleUtils.Legacy

# Imports the PowerShell my_util in the my_namesapce.my_name collection
#AnsibleRequires -PowerShell ansible_collections.my_namespace.my_name.plugins.module_utils.my_util

# Imports the PowerShell my_util that exists in the same collection as the current module
#AnsibleRequires -PowerShell ..module_utils.my_util

# Imports the PowerShell Ansible.ModuleUtils.Optional provided by Ansible if it exists.
# If it does not exist then it will do nothing.
#AnsibleRequires -PowerShell Ansible.ModuleUtils.Optional -Optional

# Imports the C# Ansible.Process provided by Ansible itself
#AnsibleRequires -CSharpUtil Ansible.Process

# Imports the C# my_util in the my_namespace.my_name collection
#AnsibleRequires -CSharpUtil ansible_collections.my_namespace.my_name.plugins.module_utils.my_util

# Imports the C# my_util that exists in the same collection as the current module
#AnsibleRequires -CSharpUtil ..module_utils.my_util

# Imports the C# Ansible.Optional provided by Ansible if it exists.
# If it does not exist then it will do nothing.
#AnsibleRequires -CSharpUtil Ansible.Optional -Optional

对于可选的 require 语句,模块代码需要验证是否已导入 util,然后再尝试使用它。这可以通过检查 util 提供的函数或类型是否存在来完成。

虽然 #Requires -Module#AnsibleRequires -PowerShell 都可用于加载 PowerShell 模块,但建议使用 #AnsibleRequires。这是因为 #AnsibleRequires 支持集合模块 util、按相对 util 名称导入和可选的 util 导入。

C# 模块 util 可以通过将 using Ansible.<module_util>; 行添加到脚本顶部以及所有其他 using 语句来引用其他 C# util。

Windows 模块实用程序

与 Python 模块一样,PowerShell 模块还提供了许多模块实用程序,这些实用程序在 PowerShell 中提供辅助函数。可以通过将以下行添加到 PowerShell 模块来导入这些 module_utils:

#Requires -Module Ansible.ModuleUtils.Legacy

这将导入位于 ./lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1 的 module_util,并启用调用其所有函数。从 Ansible 2.8 开始,Windows 模块 util 也可以用 C# 编写并存储在 lib/ansible/module_utils/csharp 中。可以通过将以下行添加到 PowerShell 模块来导入这些 module_utils:

#AnsibleRequires -CSharpUtil Ansible.Basic

这将导入位于 ./lib/ansible/module_utils/csharp/Ansible.Basic.cs 的 module_util,并自动加载执行过程中的类型。C# 模块 util 可以通过将以下行添加到 util 顶部的 using 语句来相互引用并一起加载:

using Ansible.Become;

可以在 C# 文件中设置特殊注释以控制编译参数。可以将以下注释添加到脚本:

  • //AssemblyReference -Name <assembly dll> [-CLR [Core|Framework]]: 在编译期间要引用的程序集 DLL,可选的 -CLR 标志也可用于声明在 .NET Core、Framework 或两者 (如果省略) 下运行时是否引用。

  • //NoWarn -Name <error id> [-CLR [Core|Framework]]: 编译代码时要忽略的编译器警告 ID,可选的 -CLR 的工作方式与上面相同。警告列表可以在 编译器错误 中找到。

此外,还定义了以下预处理器符号:

  • CORECLR:当 PowerShell 通过 .NET Core 运行时,存在此符号。

  • WINDOWS:当 PowerShell 在 Windows 上运行时,存在此符号。

  • UNIX:当 PowerShell 在 Unix 上运行时,存在此符号。

这些标志的组合有助于使模块 util 在 .NET Framework 和 .NET Core 上都可互操作,以下是一个实际应用的示例:

#if CORECLR
using Newtonsoft.Json;
#else
using System.Web.Script.Serialization;
#endif

//AssemblyReference -Name Newtonsoft.Json.dll -CLR Core
//AssemblyReference -Name System.Web.Extensions.dll -CLR Framework

// Ignore error CS1702 for all .NET types
//NoWarn -Name CS1702

// Ignore error CS1956 only for .NET Framework
//NoWarn -Name CS1956 -CLR Framework

以下是 Ansible 打包的 module_utils 列表,以及它们的一般用途描述:

  • ArgvParser: 用于将参数列表转换为符合 Windows 参数解析规则的转义字符串的实用程序。

  • CamelConversion: 用于将 camelCase 字符串/列表/字典转换为 snake_case 的实用程序。

  • CommandUtil: 用于执行 Windows 进程并将 stdout/stderr 和 rc 作为单独的对象返回的实用程序。

  • FileUtil: 扩展了 Get-ChildItemTest-Path 以处理特殊文件(如 C:\pagefile.sys)的实用程序。

  • Legacy: Ansible 模块的通用定义和辅助实用程序。

  • LinkUtil: 用于创建、删除和获取有关符号链接、连接点和硬链接的信息的实用程序。

  • SID: 用于将用户或组转换为 Windows SID 和反之的实用程序。

有关任何特定模块实用程序及其要求的更多详细信息,请参阅Ansible 模块实用程序源代码

PowerShell 模块实用程序可以存储在标准 Ansible 发行版之外,以便与自定义模块一起使用。自定义 module_utils 放置在名为 module_utils 的文件夹中,该文件夹位于 playbook 或角色目录的根文件夹中。

C# 模块实用程序也可以存储在标准 Ansible 发行版之外,以便与自定义模块一起使用。与 PowerShell 实用程序类似,这些实用程序存储在名为 module_utils 的文件夹中,并且文件名必须以扩展名 .cs 结尾,以 Ansible. 开头,并以实用程序中定义的命名空间命名。

下面的示例是一个角色结构,其中包含两个名为 Ansible.ModuleUtils.ModuleUtil1Ansible.ModuleUtils.ModuleUtil2 的 PowerShell 自定义模块实用程序,以及一个包含命名空间 Ansible.CustomUtil 的 C# 实用程序

meta/
  main.yml
defaults/
  main.yml
module_utils/
  Ansible.ModuleUtils.ModuleUtil1.psm1
  Ansible.ModuleUtils.ModuleUtil2.psm1
  Ansible.CustomUtil.cs
tasks/
  main.yml

每个 PowerShell 模块实用程序必须至少包含一个函数,该函数已在文件末尾使用 Export-ModuleMember 导出。例如

Export-ModuleMember -Function Invoke-CustomUtil, Get-CustomInfo

公开共享的模块选项

PowerShell 模块实用程序可以轻松公开模块在构建其参数规范时可以使用的通用模块选项。这允许将常用功能存储和维护在一个位置,并使多个模块只需最少的工作即可使用这些功能。添加到这些实用程序之一的任何新功能或错误修复,然后将自动被调用该实用程序的各种模块使用。

一个例子是拥有一个处理针对 API 的身份验证和通信的模块实用程序。此实用程序可被多个模块使用,以公开一组通用的模块选项,例如 API 端点、用户名、密码、超时、证书验证等等,而无需将这些选项添加到每个模块规范中。

具有共享参数规范的模块实用程序的标准约定是具有

  • 一个 Get-<namespace.name.util name>Spec 函数,该函数输出模块的通用规范
    • 强烈建议使此函数名称对于模块是唯一的,以避免与可以加载的其他实用程序发生任何冲突

    • 输出规范的格式是 Hashtable,其格式与用于普通模块的 $spec 相同

  • 一个接受名为 -Module 参数下的 AnsibleModule 对象的函数,该对象可用于获取共享选项

由于这些选项可以在各种模块之间共享,因此强烈建议在共享规范中保持模块选项名称和别名尽可能具体。例如,不要使用名为 password 的实用程序选项,而应使用类似 acme_password 的唯一名称作为前缀。

警告

未能使用唯一的选项名称或别名可能会阻止模块使用该实用程序,该模块也使用这些名称或别名作为其自己的选项。

以下是一个名为 ServiceAuth.psm1 的示例模块实用程序,它在一个集合中实现了一种模块与服务进行身份验证的通用方法。

Invoke-MyServiceResource {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({ $_.GetType().FullName -eq 'Ansible.Basic.AnsibleModule' })]
        $Module,

        [Parameter(Mandatory=$true)]
        [String]
        $ResourceId,

        [String]
        $State = 'present'
    )

    # Process the common module options known to the util
    $params = @{
        ServerUri = $Module.Params.my_service_url
    }
    if ($Module.Params.my_service_username) {
        $params.Credential = Get-MyServiceCredential
    }

    if ($State -eq 'absent') {
        Remove-MyService @params -ResourceId $ResourceId
    } else {
        New-MyService @params -ResourceId $ResourceId
    }
}

Get-MyNamespaceMyCollectionServiceAuthSpec {
    # Output the util spec
    @{
        options = @{
            my_service_url = @{ type = 'str'; required = $true }
            my_service_username = @{ type = 'str' }
            my_service_password = @{ type = 'str'; no_log = $true }
        }

        required_together = @(
            ,@('my_service_username', 'my_service_password')
        )
    }
}

$exportMembers = @{
    Function = 'Get-MyNamespaceMyCollectionServiceAuthSpec', 'Invoke-MyServiceResource'
}
Export-ModuleMember @exportMembers

为了使模块利用此通用参数规范,可以按以下方式设置:

#!powershell

# Include the module util ServiceAuth.psm1 from the my_namespace.my_collection collection
#AnsibleRequires -PowerShell ansible_collections.my_namespace.my_collection.plugins.module_utils.ServiceAuth

# Create the module spec like normal
$spec = @{
    options = @{
        resource_id = @{ type = 'str'; required = $true }
        state = @{ type = 'str'; choices = 'absent', 'present' }
    }
}

# Create the module from the module spec but also include the util spec to merge into our own.
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec, @(Get-MyNamespaceMyCollectionServiceAuthSpec))

# Call the ServiceAuth module util and pass in the module object so it can access the module options.
Invoke-MyServiceResource -Module $module -ResourceId $module.Params.resource_id -State $module.params.state

$module.ExitJson()

注意

在模块规范中定义的选项始终优先于实用程序规范。实用程序规范中同一键下的任何列表值都将附加到该同一键的模块规范中。字典值将添加模块规范中缺少的任何键,并合并任何列表或字典值。这类似于扩展模块文档时文档片段插件的工作方式。

要记录模块的这些共享实用程序选项,请创建一个文档片段插件,该插件记录模块实用程序实现的选项,并扩展每个实现该实用程序的模块的模块文档,以在其文档中包含该片段。

Windows playbook 模块测试

您可以使用 Ansible playbook 测试模块。例如

  • 在任何目录中创建一个 playbook touch testmodule.yml

  • 在同一目录中创建一个清单文件 touch hosts

  • 使用连接到 Windows 主机所需变量填充清单文件。

  • 将以下内容添加到新的 playbook 文件中

---
- name: test out windows module
  hosts: windows
  tasks:
  - name: test out module
    win_module:
      name: test name
  • 运行 playbook ansible-playbook -i hosts testmodule.yml

这对于了解 Ansible 如何使用新的端到端模块运行非常有用。下面显示了其他可能的模块测试方法。

Windows 调试

目前,只能在 Windows 主机上调试模块。这在开发新模块或实施错误修复时非常有用。以下是设置此功能需要遵循的一些步骤

  • 将模块脚本复制到 Windows 服务器

  • 将文件夹 ./lib/ansible/module_utils/powershell./lib/ansible/module_utils/csharp 复制到上面脚本所在的同一目录

  • 在模块代码中,在任何 #Requires -Module 行的开头添加一个额外的 #,这仅对于任何以 #Requires -Module 开头的行才是必需的

  • 将以下内容添加到复制到服务器的模块脚本的开头

# Set $ErrorActionPreference to what's set during Ansible execution
$ErrorActionPreference = "Stop"

# Set the first argument as the path to a JSON file that contains the module args
$args = @("$($pwd.Path)\args.json")

# Or instead of an args file, set $complex_args to the pre-processed module args
$complex_args = @{
    _ansible_check_mode = $false
    _ansible_diff = $false
    path = "C:\temp"
    state = "present"
}

# Import any C# utils referenced with '#AnsibleRequires -CSharpUtil' or 'using Ansible.;
# The $_csharp_utils entries should be the context of the C# util files and not the path
Import-Module -Name "$($pwd.Path)\powershell\Ansible.ModuleUtils.AddType.psm1"
$_csharp_utils = @(
    [System.IO.File]::ReadAllText("$($pwd.Path)\csharp\Ansible.Basic.cs")
)
Add-CSharpType -References $_csharp_utils -IncludeDebugInfo

# Import any PowerShell modules referenced with '#Requires -Module`
Import-Module -Name "$($pwd.Path)\powershell\Ansible.ModuleUtils.Legacy.psm1"

# End of the setup code and start of the module code
#!powershell

您可以根据模块的需要向 $complex_args 添加更多参数,或者通过具有以下结构的 JSON 文件定义模块选项

{
    "ANSIBLE_MODULE_ARGS": {
        "_ansible_check_mode": false,
        "_ansible_diff": false,
        "path": "C:\\temp",
        "state": "present"
    }
}

有多种 IDE 可用于调试 Powershell 脚本,其中两个最受欢迎的 IDE 是

要查看 Ansible 传递给模块的参数,请按照以下步骤操作。

  • 使用 ANSIBLE_KEEP_REMOTE_FILES=1 作为 Ansible 命令的前缀,以指定 Ansible 应在服务器上保留执行文件。

  • 使用 Ansible 用于执行模块的同一用户帐户登录到 Windows 服务器。

  • 导航到 %TEMP%\..。它应该包含一个以 ansible-tmp- 开头的文件夹。

  • 在此文件夹中,打开模块的 PowerShell 脚本。

  • 在此脚本中,在 $json_raw 下有一个原始 JSON 脚本,其中包含 module_args 下的模块参数。这些参数可以手动分配给在调试脚本上定义的 $complex_args 变量,也可以放入 args.json 文件中。

Windows 单元测试

目前,没有机制在 Ansible CI 下运行 Powershell 模块的单元测试。

Windows 集成测试

Ansible 模块的集成测试通常编写为 Ansible 角色。这些测试角色位于 ./test/integration/targets 中。您必须首先设置您的测试环境,并配置一个 Ansible 连接的测试清单。

在此示例中,我们将设置一个测试清单以连接到两个主机,并为 win_stat 运行集成测试

  • 运行命令 source ./hacking/env-setup 以准备环境。

  • 创建 ./test/integration/inventory.winrm.template 的副本并将其命名为 inventory.winrm

  • [windows] 下填写条目,并设置连接到主机所需的必要变量。

  • 安装所需的 Python 模块 以支持 WinRM 和配置的身份验证方法。

  • 要执行集成测试,请运行 ansible-test windows-integration win_stat;您可以将 win_stat 替换为您要测试的角色。

这将执行当前为该角色定义的所有测试。您可以像使用 ansible-playbook 一样,使用 -v 参数设置详细级别。

在为新模块开发测试时,建议在检查模式下测试一次场景,在非检查模式下测试两次。这确保检查模式不会进行任何更改但会报告更改,并且第二次运行是幂等的且不报告更改。例如

- name: remove a file (check mode)
  win_file:
    path: C:\temp
    state: absent
  register: remove_file_check
  check_mode: true

- name: get result of remove a file (check mode)
  win_command: powershell.exe "if (Test-Path -Path 'C:\temp') { 'true' } else { 'false' }"
  register: remove_file_actual_check

- name: assert remove a file (check mode)
  assert:
    that:
    - remove_file_check is changed
    - remove_file_actual_check.stdout == 'true\r\n'

- name: remove a file
  win_file:
    path: C:\temp
    state: absent
  register: remove_file

- name: get result of remove a file
  win_command: powershell.exe "if (Test-Path -Path 'C:\temp') { 'true' } else { 'false' }"
  register: remove_file_actual

- name: assert remove a file
  assert:
    that:
    - remove_file is changed
    - remove_file_actual.stdout == 'false\r\n'

- name: remove a file (idempotent)
  win_file:
    path: C:\temp
    state: absent
  register: remove_file_again

- name: assert remove a file (idempotent)
  assert:
    that:
    - not remove_file_again is changed

Windows 通信和开发支持

加入Ansible 论坛,并使用 windows 标签讨论有关 Windows 的 Ansible 开发。