使用ruamel.yaml生成Ansible inventory文件报错排查
问题根因
两个低级问题叠加导致的报错,和YAML语法、Python代码逻辑本身无关:
- 你代码中指定的输出文件名是
db.yaml,但Ansible命令里加载的inventory是greenhouses.yml,如果没有手动重命名,Ansible实际加载的要么是旧文件、要么是其他文件,和你代码生成的内容无关。 - ruamel.yaml默认直接写入Path对象时,在Windows环境下会使用系统默认编码(通常为GBK)写入文件,同时可能附带UTF-8 BOM头;另外你写的defaultdict转换方法只处理了外层字典,嵌套的内层defaultdict在部分ruamel.yaml版本下会被加上Python专属类型标记,Ansible解析时不会报格式错误,但读取主机连接配置时会拿到None值,最终触发
expected str, bytes or os.PathLike object, not NoneType报错。
这也是为什么你手动粘贴同样内容保存就能正常运行——手动保存的文件默认是无BOM的UTF-8编码,也没有隐式类型标记。
修复代码
不要直接把Path对象传给dump方法,手动指定编码打开文件,同时递归把所有嵌套defaultdict转成普通Python字典,对齐Ansible的YAML缩进规范:
from collections import defaultdict from pathlib import Path import ruamel.yaml # 这里文件名要和Ansible -i参数指定的文件名一致,不要写错 outfile = Path('greenhouses.yml') db = defaultdict(lambda: defaultdict(list)) db['greenhouse1']['fruits'].append('apples') db['greenhouse1']['fruits'].append('oranges') db['greenhouse1']['colors'] = ["red", "orange"] db['greenhouse2']['fruits'].append('banana') # 递归转换所有嵌套defaultdict为普通dict def to_plain_dict(data): if isinstance(data, defaultdict): data = dict(data) if isinstance(data, dict): return {k: to_plain_dict(v) for k, v in data.items()} if isinstance(data, list): return [to_plain_dict(item) for item in data] return data yaml = ruamel.yaml.YAML() # 对齐Ansible默认YAML缩进风格,避免旧版本兼容性问题 yaml.indent(mapping=2, sequence=4, offset=2) # 手动指定无BOM的UTF-8编码,使用Unix风格换行写入 with open(outfile, 'w', encoding='utf-8', newline='\n') as f: yaml.dump(to_plain_dict(db), f)
验证步骤
- 生成文件后执行
ansible-inventory -i greenhouses.yml --list,确认输出的结构中greenhouse1、greenhouse2主机和对应的fruits、colors变量都被正确识别,没有异常键名。 - 再执行
ansible all -m win_ping -i greenhouses.yml测试连通性,不会再出现NoneType报错。
补充:最开始排查时被报错里的None误导,以为是inventory结构缺字段,实际上是文件编码和隐式类型标记导致Ansible读出来的配置是空值,和inventory结构、Playbook、角色逻辑都没有关系。
内容的提问来源于stack exchange,提问作者Nicholas Tripp
相关产品推荐
相关产品推荐

