news 2026/9/24 5:27:06

Salt slsutil 执行模块完全指南:SLS 文件与模板中的数据处理、渲染与文件探测工具

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Salt slsutil 执行模块完全指南:SLS 文件与模板中的数据处理、渲染与文件探测工具
  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

Software to automate the management and configuration of infrastructure and applications at scale.

项目地址:https://gitcode.com/gh_mirrors/sa/salt
点击查看免费下载

本文以 Salt 官方 API 文档 doc/ref/modules/all/salt.modules.slsutil.rst 为核心骨架,结合 salt/modules/slsutil.py 源码及其单元测试 tests/pytests/unit/modules/test_slsutil.py,系统讲解slsutil执行模块的每一个函数。读完本文,你将掌握:在 Jinja 模板与 SLS 状态文件中递归合并/更新数据结构、选用不同合并策略、调用渲染器管道渲染文件或字符串、跨格式序列化与反序列化、生成标准化的“本文件由 Salt 管理”注释横幅、把布尔值转换为任意字符串,以及在 state tree 中探测文件/目录是否存在并向上逐级查找文件——这些都是编写复杂 Salt 公式(Formula)时高频使用的实战能力。

slsutil(SLS utility)是 Salt 中一组“供 SLS 文件内部使用”的通用工具函数。它不直接管理系统资源,而是为状态文件和 Jinja 模板提供数据处理能力,是 Salt 公式开发者的“瑞士军刀”。

一、模块概览:slsutil能做什么

从 salt/modules/slsutil.py 的源码看,slsutil一共提供了 10 个公开函数,可以归为四类:

分类函数核心用途
数据合并updatemergemerge_all递归合并/更新字典,支持多种合并策略
渲染renderer将文件或字符串送入 Salt 渲染器管道(renderer pipe)处理
序列化serializedeserialize调用 Salt 的序列化器在 Python 对象与字符串/流之间转换
模板辅助bannerboolstr生成注释横幅、把布尔值输出为指定字符串
文件探测file_existsdir_existspath_existsfindup在 state tree 中检查文件/目录存在性并沿祖先目录查找

其中_set_context(版本 3004 起)与_get_serialize_fn是内部辅助函数,公开 API 文档不会单独列出,但它们是file_exists/dir_exists缓存机制与序列化函数查找的实现基础。

二、数据合并:updatemergemerge_all

2.1update:递归版本的dict.update

slsutil.update(dest, upd, recursive_update=True, merge_lists=False)upd递归合并进dest

salt '*' slsutil.update '{foo: Foo}' '{bar: Bar}' # 结果: {'foo': 'Foo', 'bar': 'Bar'}

关键参数:

  • recursive_update:默认True,表示递归合并嵌套字典;设为False则退化为经典dict.update行为(整体覆盖)。
  • merge_lists:默认False。仅在recursive_update=True时生效;设为True后,两个列表将按“追加”方式聚合(dest[key] + upd[key]),且自 2016.11.6 起重复值会被去重。

从 salt/utils/dictupdate.py 的底层实现可见其精确语义:

  • destupd的键完全不相交时,会自动降级为非递归更新(recursive_update=False),提升性能;
  • 双方均为Mapping时递归进入;
  • 双方均为listmerge_lists=True时,upd中不存在的元素才被追加进深拷贝后的dest列表;
  • 自 3008.0 起,当merge_lists=True且试图用映射覆盖列表时(strict=True场景)会抛出TypeError,避免隐式数据丢失。

单元测试 test_update 验证了键合并与merge_lists=False时的覆盖行为。

2.2merge:按策略合并

slsutil.merge(obj_a, obj_b, strategy="smart", renderer="yaml", merge_lists=False)允许通过strategy显式选择合并方式:

salt '*' slsutil.merge '{foo: Foo}' '{bar: Bar}'

支持的合并策略(定义于 salt/utils/dictupdate.py):

策略行为
smart(默认)依据renderer参数自动选择:若渲染器以yamlex结尾或以yamlex_开头则用aggregate,否则用recurse
recurse深拷贝obj_a后递归合并obj_b
aggregate使用 yamlex 序列化器的merge_recursive进行“聚合”式合并(层级合并,可处理列表语义)
list相同键的值组合成列表[obj_a[key], obj_b[key]]
overwrite仅当键存在于obj_a时用obj_b的值覆盖,再递归合并
none不真正合并(用于单 pillar 场景),行为等同 recurse
其他未知值记录 warning 日志后回退为recurse

测试用例 test_merge 展示了各种策略的典型差异,例如strategy="list"{"foo": "Foo"}{"foo": "Bar"}合并为{"foo": ["Foo", "Bar"]}

2.3merge_all:按顺序合并一组对象

slsutil.merge_all(lst, strategy="smart", renderer="yaml", merge_lists=False)(版本 2019.2.0 起)依次将列表中的每个对象合并进结果字典,后合并的值覆盖先合并的值:

salt-call --output=txt slsutil.merge_all '[{foo: Foo}, {foo: Bar}]' local: {u'foo': u'Bar'}

其实现就是循环调用salt.utils.dictupdate.merge(ret, obj, ...)strategyrenderermerge_lists三个参数与merge完全一致。这在合并多层 pillar 数据、或把多个 map 文件的结果叠加时非常有用。

三、renderer:把文件/字符串送入渲染器管道

slsutil.renderer(path=None, string=None, default_renderer="jinja|yaml", **kwargs)是模块中功能最开放的函数。它利用 Salt 的“渲染器管道”(renderer pipes)机制,将文件或内联字符串依次通过多个渲染器处理,最终返回处理结果。自 2018.3.0 起支持 Salt fileserver URI(如salt://path/to/file)。

salt '*' slsutil.renderer salt://path/to/file salt '*' slsutil.renderer /path/to/file salt '*' slsutil.renderer /path/to/file.jinja default_renderer='jinja' salt '*' slsutil.renderer /path/to/file.sls default_renderer='jinja|yaml' salt '*' slsutil.renderer string='Inline template! {{ saltenv }}' salt '*' slsutil.renderer string='Hello, {{ name }}.' name='world'

参数语义:

  • path:可以是 Salt fileserver 上的任意 URI(支持cp.get_url支持的所有 URI 形式),也可以是本地文件系统路径;
  • string:内联字符串。注意并非所有渲染器都支持字符串输入——例如py渲染器就要求文件;
  • default_renderer:默认渲染管道,会被文件开头的 shebang(如#!jinja|yaml)覆盖;
  • kwargs:透传给compile_template()的关键字参数,如name='world'会作为模板变量注入。

典型实战场景:解耦 map 文件的渲染器。Salt 公式中常见的 map 文件通常与使用它的 SLS 采用相同渲染器,但通过slsutil.renderer可以打破这一限制——map 文件用 Python 渲染器编写,而引用它的 SLS 依然使用默认的jinja|yaml。官方文档给出了两个功能等价的 map 文件示例:

jinja|yaml版本(#!jinja|yaml头):

#!jinja|yaml {% set apache = salt'grains.filter_by') %} {{ apache | yaml() }}

py版本(#!py头):

#!py def run(): apache = __salt__'grains.filter_by') return apache

无论使用哪个版本,其他任何 SLS 文件都能用同一行 Jinja 调用它:

{% set apache = salt'slsutil.renderer' %}

实现原理(salt/modules/slsutil.py):

  1. 只传pathstring之一,同时传或不传都会抛出SaltInvocationError
  2. 通过salt.loader.render(__opts__, __salt__)加载全部渲染器;
  3. path使用__salt__"cp.get_url")获取本地路径;
  4. string使用特殊占位:string:并把字符串放入kwargs["input_data"]
  5. 调用salt.template.compile_template(path_or_string, renderers, default_renderer, __opts__["renderer_blacklist"], __opts__["renderer_whitelist"], **kwargs)
  6. 最后根据stringio.is_readable判断返回 StringIO 内容还是原始返回值。

注意其中会遵守__opts__中的renderer_blacklist/renderer_whitelist配置,即 master/minion 配置里对渲染器的黑白名单在此同样生效。

选择渲染管道时的一个关键提醒:不同渲染器的产出类型不同——Jinja 处理文本并产生字符串,而 YAML 渲染器处理文本后产生的是数据结构。因此在管道中选择渲染器时,要始终清楚自己期望得到字符串还是字典/列表。

单元测试 test_renderer 覆盖了字符串渲染、缺参报错、path/string 同时传入报错以及文件渲染四种情形。

四、序列化与反序列化:serializedeserialize

slsutil.serialize(serializer, obj, **mod_kwargs)使用 Salt 已加载的序列化器将 Python 对象序列化为字符串;slsutil.deserialize(serializer, stream_or_string, **mod_kwargs)则相反。

# 注意 --no-parse=obj 防止命令行参数被提前解析 salt '*' --no-parse=obj slsutil.serialize 'json' obj="{'foo': 'Foo!'}" salt '*' slsutil.deserialize 'json' '{"foo": "Foo!"}' salt '*' --no-parse=stream_or_string slsutil.deserialize 'json' \ stream_or_string='{"foo": "Foo!"}'

Jinja 中的用法:

{% set json_string = salt'slsutil.serialize' %} {% set python_object = salt'slsutil.deserialize' %}

从 salt/serializers/ 目录可以看到当前仓库内置的序列化器:configparserjsonmsgpacktomlmodyamlyamlexserializer参数即取其中的名称,例如slsutil.serialize('yaml', obj)

实现细节_get_serialize_fn(salt/modules/slsutil.py)通过salt.loader.serializers(__opts__)加载序列化器,若序列化器不存在抛出CommandExecutionError("Serializer '<name>' not found."),若对应函数未实现则抛出CommandExecutionError("Serializer '<name>' does not implement <fn>.")。测试 test__get_serializer_fn 对这两种报错路径都有验证。

五、模板辅助:bannerboolstr

5.1banner:标准化注释横幅

配置管理的一个常见做法是在被管理的文件中插入“此文件由 Salt 管理,请勿手动修改”的注释块。slsutil.banner()让这一操作标准化、可定制:

{{ salt['slsutil.banner']() }}

输出(默认width=72commentchar='#'borderchar='#'):

######################################################################## # # # THIS FILE IS MANAGED BY SALT - DO NOT EDIT # # # # The contents of this file are managed by Salt. Any changes to this # # file may be overwritten automatically and without warning. # ########################################################################

参数说明:

参数默认值说明
width72横幅宽度(字符数)
commentchar#每行行首的注释字符,支持//等多字符序列;若文件语法不支持行注释(如 XML),改用blockstart/blockend
borderchar#上下边框字符,必须是单个字符
blockstartNone块注释起始序列,需与blockend配合(如/*
blockendNone块注释结束序列(如*/
titleTHIS FILE IS MANAGED BY SALT - DO NOT EDIT居中显示在方框顶部
text固定的警告文本左对齐显示在方框底部
newlineFalse是否在横幅末尾追加换行符

Javadoc 风格示例

{{ salt'slsutil.banner' }}
/** *********************************************************************** * * * THIS FILE IS MANAGED BY SALT - DO NOT EDIT * * * * The contents of this file are managed by Salt. Any changes to this * * file may be overwritten automatically and without warning. * *********************************************************************** */

自定义标题与正文示例

{{ set copyright='This file may not be copied or distributed without permission of VMware, Inc.' }} {{ salt'slsutil.banner' }}

实现上,banner使用textwrap.TextWrapper对标题与正文按width自动折行,并通过os.linesep拼接各行;若width过小导致内容宽度为负,会抛出ArgumentValueError。单元测试 test_banner 断言了每一行的长度恰好等于width、以commentchar开头并以commentchar.strip()结尾,以及 blockstart/blockend 出现在首尾行。

5.2boolstr:布尔值转任意字符串

slsutil.boolstr(value, true="true", false="false")把布尔值映射为模板所需的字符串,常用于把 Pillar/Grains 中的布尔配置输出为特定文件语法要求的取值(如yes/no):

{% set encrypted = salt'pillar.get' %} use_tls: {{ salt'slsutil.boolstr' }}

当 pillar 中smtp:encryptedTrue时输出use_tls: yes。默认true/false参数值分别是字符串"true""false",也可以传任意其他取值。测试 test_boolstr 验证了yes/no映射。

六、state tree 文件探测:file_existsdir_existspath_existsfindup

这四个函数(自版本 3004 起)用于在 Salt fileserver 的 state tree 中探测路径,适合在状态文件中根据文件是否存在做条件渲染。

6.1 存在性检查

salt '*' slsutil.file_exists nginx/defaults.yaml salt '*' slsutil.dir_exists nginx/files salt '*' slsutil.path_exists nginx/defaults.yaml
  • file_exists(path, saltenv="base"):文件是否存在;
  • dir_exists(path, saltenv="base"):目录是否存在;
  • path_exists(path, saltenv="base"):文件或目录是否存在(即前两者的或)。

三个函数都接受saltenv参数指定 fileserver 环境,默认basepath_exists的实现就是file_exists(...) or dir_exists(...)

性能设计file_existsdir_exists通过_set_contextcp.list_master(saltenv)/cp.list_master_dirs(saltenv)的结果缓存在__context__["slsutil"][saltenv]下(CONTEXT_BASE = "slsutil"),同一次 minion 运行内重复探测不会反复拉取文件列表。_set_context会按需逐级创建字典路径,并支持force参数强制刷新缓存。

6.2findup:沿祖先目录向上查找

slsutil.findup(startpath, filenames, saltenv="base")从指定目录开始,逐级向上(到 state tree 根)查找文件名/目录名,返回第一个匹配的完整路径。

salt '*' slsutil.findup formulas/shared/nginx map.jinja

Jinja 中最典型的用法是配合tplfile(当前正在处理的模板文件路径),从当前状态文件所在目录向上找到最近的defaults.yaml

{{ salt"slsutil.findup" }}

行为细节(salt/modules/slsutil.py):

  • startpath为空字符串或None时从 state tree 根开始查找;
  • filenames接受单个字符串或字符串列表,也支持目录名;
  • 起始路径本身必须存在于 state tree,否则抛出SaltInvocationError
  • 一路向上到根仍未找到,抛出CommandExecutionError("File pattern(s) not found in path ancestry")

单元测试 test_findup 验证了向上查找、多文件名优先级、根目录查找以及各类异常路径。

七、在 SLS 与 Jinja 中的综合实战示例

将以上能力组合,可以实现非常灵活的公式编写模式。例如,一个 nginx 公式的状态文件可以这样写:

{%- set tplroot = tpldir.split('/')[0] %} {%- set sls_pillar = salt'pillar.get' %} {%- set defaults = salt'slsutil.renderer', default_renderer='jinja|yaml' ) %} {%- set settings = salt'slsutil.merge' %} # 合并后的配置通过 serialize 输出为 JSON,供其他工具消费 {%- set json_conf = salt'slsutil.serialize' %} nginx: pkg.installed: [] service.running: [] file.managed: - name: /etc/nginx/nginx.conf - source: salt://{{ tplroot }}/files/nginx.conf.jinja - template: jinja - context: settings: {{ settings | json }}

其中findup负责“找到离当前状态文件最近的 defaults.yaml”,renderer负责以指定渲染管道读取它,merge负责把 pillar 覆盖值合并进去,serialize负责把最终结果导出为其他格式——这正是四个核心函数在同一场景下的协同工作。

八、API 文档与测试的对应关系

  • API 文档:slsutil的官方文档入口位于 doc/ref/modules/all/salt.modules.slsutil.rst,通过automodule指令从源码 docstring 自动生成,因此在 salt/modules/slsutil.py 中各函数的 docstring 即文档本体;
  • 单元测试:tests/pytests/unit/modules/test_slsutil.py 覆盖update/merge/merge_all/renderer/serialize/deserialize/banner/boolstr/file_exists/dir_exists/path_exists/findup及两个内部辅助函数,是理解各函数精确行为的最佳补充材料;
  • 底层依赖:合并逻辑的核心实现在 salt/utils/dictupdate.py,序列化依赖 salt/serializers/ 下各序列化器;
  • 集成测试:slsutil在 SSH 模式(salt-ssh)下同样可用,相关测试见 tests/pytests/integration/ssh/test_slsutil.py 与 tests/pytests/unit/client/ssh/wrapper/test_slsutil.py。

结语

slsutil是一组“小而精”的工具:update/merge/merge_all解决了 SLS 开发中最常见的数据合并问题,renderer打破了 map 文件与 SLS 渲染器之间的耦合,serialize/deserialize打通了对象与文本格式之间的转换,banner/boolstr简化了模板输出,而file_exists/dir_exists/path_exists/findup让状态文件具备基于 state tree 实际内容的感知能力。掌握这组函数,可以让你的 Salt 公式更健壮、更灵活,也更易于维护。

  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

Software to automate the management and configuration of infrastructure and applications at scale.

项目地址:https://gitcode.com/gh_mirrors/sa/salt
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 5:25:40

Kali Linux 2026.2虚拟机配置与安全测试工具链实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 5:20:52

Marchand巴伦设计实战:从耦合线原理到宽带差分转换

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 5:18:05

ESP32 I2S驱动D类功放:从协议原理到ESP-IDF实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 5:16:31

ESP32无线图像传输实战:WebSocket实时视频流方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 5:05:11

Wandb Core 中的 gax-go v2:从 2.4 到 2.25 的能力演进与源码级解析

机器学习深度学习数据可视化可观测性 【免费下载链接】wandb The AI developer platform. Use Weights & Biases to train and fine-tune models, and manage models from experimentation to production. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/wa/wandb 点…

作者头像 李华
网站建设 2026/9/24 5:03:00

谢希仁《计算机网络》课后答案使用指南:版本对比与高效刷题法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华