命令运行器指南
简介
ansible_collections.community.general.plugins.module_utils.cmd_runner 模块实用程序提供 CmdRunner 类来帮助执行外部命令。此类是标准 AnsibleModule.run_command() 方法的包装器,用于处理命令参数、本地化设置、输出处理、检查模式和其他功能。
当一个命令在多个模块中使用时,它就更加有用,这样你就可以在一个模块实用程序文件中定义所有选项,每个模块使用具有不同参数的相同运行器。
为清晰起见,在本指南中,除非另有说明,否则我们使用术语 *选项* 指的是 Ansible 模块选项,使用术语 *参数* 指的是外部命令的命令行参数。
快速入门
CmdRunner 定义一个命令和一组关于如何格式化命令行参数(特定顺序)以进行特定执行的编码指令。它依赖于 ansible.module_utils.basic.AnsibleModule.run_command() 来实际执行命令。还有其他功能,请在本文件的其余部分查看更多详细信息。
要使用 CmdRunner,你必须首先创建一个对象。下面的示例是 community.general.ansible_galaxy_install 中实际代码的简化版本。
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
runner = CmdRunner(
    module,
    command="ansible-galaxy",
    arg_formats=dict(
        type=cmd_runner_fmt.as_func(lambda v: [] if v == 'both' else [v]),
        galaxy_cmd=cmd_runner_fmt.as_list(),
        upgrade=cmd_runner_fmt.as_bool("--upgrade"),
        requirements_file=cmd_runner_fmt.as_opt_val('-r'),
        dest=cmd_runner_fmt.as_opt_val('-p'),
        force=cmd_runner_fmt.as_bool("--force"),
        no_deps=cmd_runner_fmt.as_bool("--no-deps"),
        version=cmd_runner_fmt.as_fixed("--version"),
        name=cmd_runner_fmt.as_list(),
    )
)
这应该只做一次,然后每次需要执行命令时,你创建一个上下文并根据需要传递值。
# Run the command with these arguments, when values exist for them
with runner("type galaxy_cmd upgrade force no_deps dest requirements_file name", output_process=process) as ctx:
    ctx.run(galaxy_cmd="install", upgrade=upgrade)
# version is fixed, requires no value
with runner("version") as ctx:
    dummy, stdout, dummy = ctx.run()
# passes arg 'data' to AnsibleModule.run_command()
with runner("type name", data=stdin_data) as ctx:
    dummy, stdout, dummy = ctx.run()
# Another way of expressing it
dummy, stdout, dummy = runner("version").run()
请注意,你可以在调用 run() 时传递参数的值,否则 CmdRunner 使用名称完全相同的模块选项来为运行器参数提供值。如果未传递值且未找到指定名称的模块选项,则会引发异常,除非该参数使用 cmd_runner_fmt.as_fixed 作为格式函数,例如上面的示例中的 version。请在下面查看更多相关信息。
在第一个示例中,type、force、no_deps 等的值直接来自模块,而 galaxy_cmd 和 upgrade 是显式传递的。
注意
无法自动检索子选项的值。
这会生成类似于以下的命令行(示例取自集成测试的输出)
[
    "<venv>/bin/ansible-galaxy",
    "collection",
    "install",
    "--upgrade",
    "-p",
    "<collection-install-path>",
    "netbox.netbox",
]
参数格式
如示例所示,CmdRunner 期望一个名为 arg_formats 的参数,该参数定义如何格式化每个 CLI 命名的参数。“参数格式”只不过是一个函数,用于将变量的值转换为为命令行格式化的内容。
参数格式函数
arg_format 函数的定义形式类似于
def func(value):
    return ["--some-param-name", value]
参数 value 可以是任何类型——尽管有一些便捷机制可以帮助处理序列和映射对象。
预期结果的类型为 Sequence[str] 类型(最常见的是 list[str] 或 tuple[str]),否则它被认为是 str,并将其强制转换为 list[str]。当实际使用该参数时,此生成的字符串序列将添加到命令行。
例如,如果 func 返回
- ["nee", 2, "shruberries"],则命令行将添加参数- "nee" "2" "shruberries"。
- 2 == 2,则命令行将添加参数- True。
- None,则命令行将添加参数- None。
- [],则命令行不会为该特定参数添加任何命令行参数。
便捷格式方法
与 CmdRunner 相同的模块中有一个类 cmd_runner_fmt,它提供一组便捷方法,这些方法返回针对常见情况的格式函数。快速入门 部分的第一段代码中你可以看到该类的导入。
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
同一个例子展示了如何在CmdRunner对象的实例化中使用其中一些方法。下面是对每个可用便捷方法的描述以及使用方法的示例。在这些描述中,value指的是传递给格式化函数的单个参数。
- cmd_runner_fmt.as_list()
- 此方法不接收任何参数,函数原样返回 - value。- 创建
- cmd_runner_fmt.as_list()
 
- 示例
- 值 - 结果 - ["foo", "bar"]- ["foo", "bar"]- "foobar"- ["foobar"]
 
 
 
- cmd_runner_fmt.as_bool()
- 此方法接收两个不同的参数: - args_true和- args_false,后者是可选的。如果- value的布尔值评估结果为- True,则格式化函数返回- args_true。如果布尔值评估结果为- False,则函数返回- args_false(如果已提供),否则返回- []。- 创建(一个参数)
- cmd_runner_fmt.as_bool("--force")
 
- 示例
- 值 - 结果 - True- ["--force"]- False- []
 
- 创建(两个参数,None被视为False)
- cmd_runner_fmt.as_bool("--relax", "--dont-do-it")
 
- 创建(两个参数,
- 示例
- 值 - 结果 - True- ["--relax"]- False- ["--dont-do-it"]- ["--dont-do-it"]
 
- 创建(两个参数,忽略None)
- cmd_runner_fmt.as_bool("--relax", "--dont-do-it", ignore_none=True)
 
- 创建(两个参数,忽略
- 示例
- 值 - 结果 - True- ["--relax"]- False- ["--dont-do-it"]- []
 
 
 
- cmd_runner_fmt.as_bool_not()
- 此方法接收一个参数,当 - value的布尔值评估结果为- False时,函数返回该参数。- 创建
- cmd_runner_fmt.as_bool_not("--no-deps")
 
- 示例
- 值 - 结果 - True- []- False- ["--no-deps"]
 
 
 
- cmd_runner_fmt.as_optval()
- 此方法接收一个参数 - arg,函数返回- arg和- value的字符串连接。- 创建
- cmd_runner_fmt.as_optval("-i")
 
- 示例
- 值 - 结果 - 3- ["-i3"]- foobar- ["-ifoobar"]
 
 
 
- cmd_runner_fmt.as_opt_val()
- 此方法接收一个参数 - arg,函数返回- [arg, value]。- 创建
- cmd_runner_fmt.as_opt_val("--name")
 
- 示例
- 值 - 结果 - abc- ["--name", "abc"]
 
 
 
- cmd_runner_fmt.as_opt_eq_val()
- 此方法接收一个参数 - arg,函数返回- {arg}={value}形式的字符串。- 创建
- cmd_runner_fmt.as_opt_eq_val("--num-cpus")
 
- 示例
- 值 - 结果 - 10- ["--num-cpus=10"]
 
 
 
- cmd_runner_fmt.as_fixed()
- 此方法接收一个参数 - arg,函数不期望- value- 如果提供了- value,则会被忽略。函数原样返回- arg。- 创建
- cmd_runner_fmt.as_fixed("--version")
 
- 示例
- 值 - 结果 - ["--version"]- 57 - ["--version"]
 
- 注意
- 这是唯一一个格式化函数可以缺少值的特殊情况。此示例也来自快速入门中的代码。在这种情况下,模块具有确定命令版本的代码,以便它可以断言兼容性。此CLI参数没有要传递的值。 
 
 
 
- cmd_runner_fmt.as_map()
- 此方法接收一个参数 - arg,它必须是一个字典,以及一个可选参数- default。函数返回- arg[value]的计算结果。如果- value not in arg,则返回- default(如果已定义),否则返回- []。- 创建
- cmd_runner_fmt.as_map(dict(a=1, b=2, c=3), default=42)
 
- 示例
- 值 - 结果 - "b"- ["2"]- "yabadabadoo"- ["42"]
 
- 注意
- 如果未指定 - default,则无效值将返回空列表,这意味着它们会被静默忽略。
 
 
 
- cmd_runner_fmt.as_func()
- 此方法接收一个参数 - arg,它本身就是一个格式化函数,并且必须遵守上述规则。- 创建
- cmd_runner_fmt.as_func(lambda v: [] if v == 'stable' else ['--channel', '{0}'.format(v)])
 
- 注意
- 其结果完全取决于开发者提供的函数。 
 
 
 
参数格式化的其他功能
一些附加功能可作为装饰器使用
- cmd_runner_fmt.unpack args()
- 此装饰器将传入的 - value解包为元素列表。- 例如,在 - ansible_collections.community.general.plugins.module_utils.puppet中,它被用作- @cmd_runner_fmt.unpack_args def execute_func(execute, manifest): if execute: return ["--execute", execute] else: return [manifest] runner = CmdRunner( module, command=_prepare_base_cmd(), path_prefix=_PUPPET_PATH_PREFIX, arg_formats=dict( # ... _execute=cmd_runner_fmt.as_func(execute_func), # ... ), ) - 然后,在community.general.puppet中,它与以下内容一起使用: - with runner(args_order) as ctx: rc, stdout, stderr = ctx.run(_execute=[p['execute'], p['manifest']]) 
 
- cmd_runner_fmt.unpack_kwargs()
- 相反,此装饰器将传入的 - value解包为类似- dict的对象。
 
- cmd_runner_fmt.stack()
- 此装饰器假设 - value是一个序列,并将应用于序列的每个元素的包装函数的输出连接起来。- 例如,在community.general.django_check中, - database的参数格式定义为- arg_formats = dict( # ... database=cmd_runner_fmt.stack(cmd_runner_fmt.as_opt_val)("--database"), # ... ) - 当接收列表 - ["abc", "def"]时,输出为- ["--database", "abc", "--database", "def"] 
 
命令运行器
可以传递给CmdRunner构造函数的设置是
- module: AnsibleModule
- 模块实例。必填参数。 
 
- command: str | list[str]
- 要执行的命令。它可以是单个字符串(可执行文件名),也可以是字符串列表(第一个元素为可执行文件名,可选地包含固定参数)。这些参数在运行器的所有执行中都使用。除非此参数指向的可执行文件(当为 - str时为自身,当为- list时为其第一个元素)是绝对路径或包含字符- /,否则它将使用- AnsibleModule.get_bin_path()进行处理。
 
- arg_formats: dict
- 参数名称到格式化函数的映射。 
 
- default_args_order: str
- 顾名思义,参数的默认顺序。传递此参数后,无需指定 - args_order即可创建上下文。默认为- ()。
 
- check_rc: bool
- 当为 - True时,如果命令的返回码不为零,则模块将以错误退出。默认为- False。
 
- path_prefix: list[str]
- 如果正在执行的命令安装在非标准目录路径中,则可以提供其他路径来搜索可执行文件。默认为 - None。
 
- environ_update: dict
- 传递要在命令执行期间设置的其他环境变量。默认为 - None。
 
- force_lang: str
- 通常需要强制区域设置到一个特定值,以便响应一致且可解析。请注意,使用此选项(默认启用)会覆盖环境变量 - LANGUAGE和- LC_ALL。要禁用此机制,请将此参数设置为- None。在community.general 9.1.0中,为该参数引入了特殊值- auto,其效果是- CmdRunner然后尝试确定运行时的最佳可解析区域设置。将来它应该成为默认值,但目前默认值为- C。
 
创建上下文时,可以传递给调用的附加设置是
- args_order: str
- 确定在命令行中呈现参数的顺序。除非为运行器实例提供了 - default_args_order,否则此参数是必需的。
 
- output_process: func
- 将可执行文件的输出转换为不同的值或格式的函数。请参见以下部分中的示例。 
 
- check_mode_skip: bool
- 模块处于检查模式时是否跳过命令的实际执行。默认为 - False。
 
- check_mode_return: any
- 如果 - check_mode_skip=True,则返回此值。
 
- 对AnsibleModule.run_command()有效的命名参数
- 除了 - args之外,在设置运行上下文时,可以传递任何对- run_command()有效的参数。例如,- data可用于将信息发送到命令的标准输入。或者- cwd可用于在特定工作目录中运行命令。
 
- 对
此外,还可以传递AnsibleModule.run_command()的任何其他有效参数,但是如果重新定义运行器或其上下文创建中已存在的选项,则可能会出现意外行为。谨慎使用。
处理结果
如前所述,CmdRunner 使用 AnsibleModule.run_command() 执行外部命令,并将该方法的返回值传递回调用方。这意味着默认情况下,结果将是一个元组 (rc, stdout, stderr)。
如果您需要转换或处理该输出,可以将一个函数作为 output_process 参数传递给上下文。它必须是一个类似于以下的函数:
def process(rc, stdout, stderr):
    # do some magic
    return processed_value    # whatever that is
在这种情况下,run() 的返回值是由该函数返回的 processed_value。
PythonRunner
PythonRunner 类是 CmdRunner 的一个专用版本,用于执行 Python 脚本。它的构造函数中包含两个额外的互斥参数 python 和 venv。
from ansible_collections.community.general.plugins.module_utils.python_runner import PythonRunner
from ansible_collections.community.general.plugins.module_utils.cmd_runner import cmd_runner_fmt
runner = PythonRunner(
    module,
    command=["-m", "django"],
    arg_formats=dict(...),
    python="python",
    venv="/path/to/some/venv",
)
python 的默认值是字符串 python,venv 的默认值是 None。
使用 python="python3.12" 的此类命令生成的命令行类似于:
/usr/bin/python3.12 -m django <arg1> <arg2> ...
而 venv="/work/venv" 的命令行类似于:
/work/venv/bin/python -m django <arg1> <arg2> ...
您可以将 command 参数的值作为字符串提供(在这种情况下,字符串用作脚本名称)或作为列表提供,在这种情况下,列表的元素必须是 Python 解释器的有效参数,如上例所示。有关详细信息,请参阅 命令行和环境。
如果参数 python 是绝对路径,或包含目录分隔符,例如 /,则按原样使用它,否则将搜索运行时的 PATH 以查找该命令名称。
除此之外,其他所有功能都与 CmdRunner 中的一样。
4.8.0 版本新增。
