命令运行器指南

简介

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。请在下面查看更多相关信息。

在第一个示例中,typeforceno_deps 等的值直接来自模块,而 galaxy_cmdupgrade 是显式传递的。

注意

无法自动检索子选项的值。

这会生成类似于以下的命令行(示例取自集成测试的输出)

[
    "<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_trueargs_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,函数返回argvalue的字符串连接。

    • 创建

      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

    通常需要强制区域设置到一个特定值,以便响应一致且可解析。请注意,使用此选项(默认启用)会覆盖环境变量LANGUAGELC_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 脚本。它的构造函数中包含两个额外的互斥参数 pythonvenv

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 的默认值是字符串 pythonvenv 的默认值是 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 版本新增。