首页
/ Ansible hacking 开发工具指南:用 env-setup 搭建源码树开发环境,用 test-module.py 与 return_skeleton_generator.py 本地调试模块

Ansible hacking 开发工具指南:用 env-setup 搭建源码树开发环境,用 test-module.py 与 return_skeleton_generator.py 本地调试模块

2026-09-04 15:17:33作者:蔡丛锟

本文以 hacking/README.md 为主体,讲解 Ansible 仓库中 hacking/ 目录下的三件开发者核心工具:用 env-setup 脚本在不安装的情况下直接从 git 检出运行源码树、用 test-module.py 在本地脱离完整执行器单独运行某个模块(含断点调试)、以及用 return_skeleton_generator.py 从模块 JSON 输出自动生成 RETURNS 文档骨架。读完本文,你可以独立搭建 Ansible 源码级开发环境,并对任意模块完成"本地单步调试 → 收集返回数据 → 生成文档"的完整开发闭环。

一、hacking/ 目录的定位

Ansible 的 hacking/ 目录是一组面向模块开发者与测试者的辅助脚本集合,核心目标是让你绕过完整的 Ansible 执行器(executor),直接在当前机器上运行、调试单个模块。hacking/README.md 官方说明了三个主要工具:

  • env-setup:修改环境变量,使你能用受支持的 Python 版本直接从 git 检出(checkout)运行 ansible;
  • test-module.py:让模块开发者(或测试者)在本地、脱离 ansible 程序本身运行一个模块;
  • return_skeleton_generator.py:根据模块的 JSON 输出(文件参数或 stdin 传入)生成模块 RETURNS 文档段落的骨架。

二、env-setup:从 git 检出直接运行 Ansible

hacking/env-setup 是一个 POSIX shell 脚本(#!/bin/sh),必须用 source 方式加载,这样它修改的环境变量才会作用于当前 shell。README 给出的标准用法是:

source ./hacking/env-setup

2.1 前提依赖

脚本本身不管理依赖,只假设你本机已具备可用 Python(hacking/env-setup 中通过 PYTHON=$(command -v python3 || command -v python) 探测 python3,找不到再退回 python)。如果你希望依赖来自 pip 而非系统包管理器,可按 hacking/README.md 的说明安装:

python -Im ensurepip  # if pip is not already available
pip install -r requirements.txt

其中 requirements.txt 声明的运行期依赖为:jinja2 >= 3.1.0(3.1.0 起修复 Jinja2 native macro 支持)、PyYAML >= 5.1cryptographypackagingresolvelib >= 0.8.0, < 2.0.0ansible-galaxy 的依赖解析器)。文件头部注释特别说明:这份 requirements 是"最宽松"的运行依赖集,仅列必需包而非经过测试的固定版本集。

2.2 脚本实际做了什么

hacking/env-setup 源码逐段看,它做了四件事:

  1. 定位仓库根目录并导出 ANSIBLE_DEV_HOME(L35-L48):脚本用 $BASH_SOURCE / $0 / $KSH_VERSION 多路分支确定 HACKING_DIR(注释说明 source$0 会变成 bash,所以必须优先取 BASH_SOURCE),再用 Python 的 os.path.realpath 解析符号链接后取父目录,export ANSIBLE_DEV_HOME
  2. 前插路径变量(L50-L58):通过幂等的 prepend_path 函数,把以下前缀加入对应环境变量(若已存在则跳过,因此可以安全地重复 source):
    • PYTHONPATH$ANSIBLE_DEV_HOME/lib(核心库 lib/ansible)与 $ANSIBLE_DEV_HOME/test/libansible_test 库);
    • PATH$ANSIBLE_DEV_HOME/binansible-playbookansible-test 等可执行入口);
    • MANPATH$ANSIBLE_DEV_HOME/docs/man
  3. 清理 .pyc 缓存(L65-L73):在 ANSIBLE_DEV_HOME 下执行 find . -type f -name "*.pyc" -exec rm -f {} \;,避免源码修改后命中旧字节码导致"改动不生效"的困惑。
  4. 打印生效结果(L75-L88):输出最终的 PATH / PYTHONPATH / MANPATH,并提示可用 -i 指定 inventory 文件。

脚本支持 -q 参数静默运行(source ./hacking/env-setup -q),此时 verbosity=silent:跳过 .pyc 清理的输出与末尾的状态打印。

仓库还附带 Fish shell 版本 hacking/env-setup.fish,逻辑等价:探测 PYTHON_BIN、用 status -f 取脚本目录、前插 PYTHONPATH/PATH/MANPATH、清理 .pyc,支持 --quiet。Fish 用户使用 . ./hacking/env-setup 加载。

补充一点定位关系:context/dev-environment.md 指出,开发环境有三条路径可选——可编辑安装 pip install -e .、source hacking/env-setup,或完全不安装地直接 bin/ansible-test sanity -v --docker default 跑测试。env-setup 属于其中的"免安装"路线,适合不想污染虚拟环境、或只需要临时从源码树跑 CLI 的场景。

三、test-module.py:脱离 Ansible 执行器本地运行模块

hacking/test-module.py(约 312 行)是模块开发者的主力调试工具。README 给出的最小示例:

./hacking/test-module.py -m lib/ansible/modules/command.py -a "echo hi"

它的价值在于:模块被打包成 AnsiballZ 单文件、在远端以特定方式启动、参数以 JSON 参数文件传入——这些流程在开发阶段都会增加调试成本。test-module.py 在本机完整复刻了这套流程,因此你可以直接对模块插入断点(README 原话:"This is a good way to insert a breakpoint into a module, for instance"),也可以在脚本中 print 观察中间状态而不必走 ansible-playbook

3.1 完整命令行选项

hacking/test-module.pyparse()(L56-L88)整理出全部选项:

选项 含义 默认值
-m, --module-path 必填,待执行模块源码的完整路径 无(缺失时打印帮助并退出)
-a, --args 模块参数字符串(支持 kv、JSON/YAML 文档、@文件 三种形式) ""
-D, --debugger Python 调试器路径(如 /usr/bin/pdb),指定后进入交互调试而非直接执行 不调试
-I, --interpreter 指定解释器,格式 TYPE=PATH(如 ansible_python_interpreter=/usr/bin/python);TYPE 不以 ansible_ 开头或不以 _interpreter 结尾时会自动补全 ansible_python_interpreter=<当前 sys.executable>
-c, --check 以 check mode 运行模块(向参数注入 _ansible_check_mode=True 关闭
-n, --noexecute 只生成模块产物、不执行 默认执行(store_false
-o, --output 生成模块文件的落盘位置 ~/.ansible_module_generated

脚本头部注释(L25-L29)给出的官方示例也值得记住:

./hacking/test-module.py -m lib/ansible/modules/command.py -a "/bin/sleep 3"
./hacking/test-module.py -m lib/ansible/modules/command.py -a "/bin/sleep 3" --debugger /usr/bin/pdb
./hacking/test-module.py -m lib/ansible/modules/lineinfile.py -a "dest=/etc/exports line='/srv/home hostname1(rw,sync)'" --check
./hacking/test-module.py -m lib/ansible/modules/command.py -a "echo hello" -n -o "test_hello"

3.2 复合参数的两种高级写法

README 特别演示了复杂参数的用法。对于如下 YAML 参数:

parent:
  child:
    - item: first
      val: foo
    - item: second
      val: boo

可以直接把等价的 JSON 作为 -a 参数传入:

./hacking/test-module.py -m module \
  -a '{"parent": {"child": [{"item": "first", "val": "foo"}, {"item": "second", "val": "bar"}]}}'

结合源码(hacking/test-module.py boilerplate_module(),L132-L163)可以看到脚本实际支持三种参数形态,优先级依次判断:

  1. -a@ 开头:视为 YAML/JSON 文件路径(注释说明"JSON 是 YAML 的子集"),用 DataLoader.load_from_file 读入并与内建参数合并,字符串参数清空;
  2. -a{ 开头:视为内联 YAML 文档(loader.load(args) 解析);
  3. 其余情况:按 kv 语法用 lib/ansible/parsing/splitter.pyparse_kv 解析。

无论哪种形态,脚本都会先注入一组与真实执行一致的"内建复杂参数"(boilerplate_module,L144-L150):

  • _ansible_selinux_special_fsC.DEFAULT_SELINUX_SPECIAL_FS
  • _ansible_tmpdirC.DEFAULT_LOCAL_TMP
  • _ansible_keep_remote_filesC.DEFAULT_KEEP_REMOTE_FILES
  • _ansible_version ← 当前 __version__
  • -c:追加 _ansible_check_mode=True

这意味着 test-module.py 下模块拿到的内建变量与生产执行路径保持一致,调试结论可以迁移到真实 playbook。

3.3 内部流程:复刻 AnsiballZ 打包 → 解压 → 执行

理解 test-module.py 的关键在于它忠实模拟了 lib/ansible/executor/module_common.py 中模块执行的全链路:

  1. 打包main()(L285-L290)先 init_plugin_loader(),再调用 module_common.modify_module(...)(定义见 lib/ansible/executor/module_common.py#L1545)把模块源码、所需 module_utils、参数与 Templar 组合成最终的模块字节串,产物写入 -o 指定的文件(默认 ~/.ansible_module_generated),并判定 module_stylenew / ansiballz / old 等);若检测到 _ANSIBALLZ_WRAPPER = True 则标记为 ansiballz 风格(L184-L185)。
  2. 解压(explode):对 ansiballz 风格,ansiballz_setup()(L198-L234)以指定解释器执行 <module> explode,从输出的第二行解析出临时目录,再在 ansible/modulesansible_collections/*/*/plugins/modulesansible/legacy 三类目录中定位被解压出的模块源码与 args 参数文件——这正是远端执行的原始形态,因此对 AnsiballZ 模块的调试所见即所得。
  3. 执行/调试runtest()(L237-L270)以子进程运行模块,捕获 stdout/stderr,先打印 RAW OUTPUT(原始 JSON),再 json.loads 并打印 PARSED OUTPUT(4 空格缩进、键排序的美化 JSON);rundebug()(L273-L282)则改为 subprocess.call("<debugger> <modfile> <argsfile>") 进入 pdb 等调试器。对 old/non_native_want_json/binary 风格,write_argsfile()(L107-L114)会把参数写入 ~/.ansible_test_module_arguments 供旧式模块读取。
  4. 清理mainfinally(L308-L312)会 shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) 删除本地临时目录,不留垃圾。

一个实用组合是:-n -o test_hello 只打包不执行,检查生成物;-D /usr/bin/pdb 直接进断点;-c 验证 check mode 分支。

四、return_skeleton_generator.py:从 JSON 输出生成 RETURNS 文档骨架

hacking/return_skeleton_generator.py(约 97 行)解决的是模块文档中 RETURNS 段落的编写成本问题:它的输入是模块的 JSON 输出(文件参数或 stdin),输出是一段可直接粘贴进模块文档字符串的 YAML。文件头部注释明确提示:获取 JSON 输出的最简单方式就是用 hacking/test-module.py;生成结果"很可能需要你去掉敏感数据、确认 returned 取值并补写有意义的 description"——每个 key 的 description 初始为 FIXME *** add description for <key>returned 默认填 always

4.1 用法

# 通过文件参数
./hacking/test-module.py -m lib/ansible/modules/command.py -a "echo hi" | \
  ./hacking/return_skeleton_generator.py
# 或先落盘再喂给生成器
./hacking/return_skeleton_generator.py module_output.json

4.2 类型推断规则

从源码看(get_return_data(),L51-L69),生成器的推断规则是:

  • 值为 dict,或"元素全为 dict 的非空 list" → type: complex,递归展开为 contains(对 list 只取首个元素 value[0] 做推断);
  • 其他标量 → typetype(value).__name__,并附带 sample 样例值;Python 的 unicode 类型会被改写为文档约定的 str
  • main()(L81-L93)会主动删除 invocation(它是每次调用都出现的元数据,不属于模块返回语义);输出用 yaml.safe_dump(default_flow_style=False) 展开成块状 YAML,并通过自定义 represent_ordereddict 保持字段顺序(summary 类字段在前,contains 在后)。

这与第三节形成完整工作流:test-module.pyPARSED OUTPUT 就是标准的模块 JSON 返回,可以直接管道进生成器,得到带 description/returned/type/contains 结构的骨架,再人工补全描述即可。

五、hacking/ 目录的其余工具(辅助了解)

除 README 三件套外,hacking/ 目录还包含几个低频但实用的工具,与本文主题同属开发辅助生态:

  • hacking/ansible-profile.py(38 行):用 cProfile 给任意 Ansible CLI 做性能剖析,用法形如 ./hacking/ansible-profile.py playbook <args>——它通过把子程序名映射到 ansible.cli.<target> 模块并取 <Target>CLI 类动态实例化(L10-L12, L30-L38),因此对 ansible-playbookansible-galaxy 等均可用;
  • hacking/report.py:测试报告相关辅助脚本(约 224 行);
  • hacking/backport/:上游补丁反向回合到维护分支的辅助工具(backport_of_line_adder.py 等);
  • hacking/tests/gen_distribution_version_testcase.py:生成分布板版本测试用例;
  • hacking/ticket_stubs/:issue 回复模板集合,偏社区流程而非技术调试。

六、小结:一条模块开发调试主线

把三个工具串起来,就是 Ansible 模块开发的标准本地回路:

  1. source ./hacking/env-setup 让 shell 直接从源码树加载 lib/test/lib/bin/
  2. ./hacking/test-module.py -m lib/ansible/modules/<module>.py -a "..." -D /usr/bin/pdb 单步调试模块本体,参数内建变量与生产执行保持一致(见 hacking/test-module.py boilerplate_module() 的实现);
  3. test-module.py 打印的 JSON 返回喂给 ./hacking/return_skeleton_generator.py,快速生成 RETURNS 骨架,人工补全描述后写入模块文档。

适用前提与限制:以上流程面向 POSIX 开发机(Windows 下建议 WSL,见 context/dev-environment.md);env-setup 依赖 checkout 自带的 lib/test/lib/bin/ 布局,不适用于已安装到 site-packages 的场景;test-module.py 走的是本机子进程,无法覆盖需要特定远端环境(如 Windows PowerShell)的模块行为,这类场景应回到 ansible-test 集成测试体系验证。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384