chef生成xml模板必须用erb,需手动转义特殊字符、显式指定cookbook和source,并用数组遍历替代硬编码节点以确保xml合法性。

模板语法必须用 ERB,不能用 Mustache 或 Liquid
Chef 默认只支持 ERB 模板引擎生成动态内容,XML 文件本质是纯文本,但 XML 的格式敏感性比 HTML 更高——比如未闭合标签、非法字符、编码不一致都会导致解析失败。所以不能指望 template 资源自动“理解” XML 结构,它只是把 ERB 渲染后的字符串原样写入文件。
常见错误是误以为 Chef 有 XML 专用模板机制,或试图在 .xml.erb 中混用 和原始 XML 实体(如 &),结果渲染出无效 XML。
- 所有变量插值必须包裹在
中,且确保输出内容已做 XML 转义(见下一条) - 避免直接拼接 XML 标签字符串,优先用嵌套结构 + 条件判断控制节点存在性
- 若需输出特殊字符(如
&,, <code>>),必须用CGI.escapeHTML或手动替换,否则生成的 XML 会非法
用 CGI.escapeHTML 转义动态字段值
XML 不允许在文本内容中直接出现 &、、<code>>,而 Chef 的 默认不做转义。例如:@app_name = "Order & Payment" 直接插入会变成 <name>Order & Payment</name>,XML 解析器会报错 Invalid character in entity name。
正确做法是在模板中显式调用转义函数:
<?xml version="1.0" encoding="UTF-8"?> <config> <app_name><%= CGI.escapeHTML(@app_name) %></app_name> <timeout><%= @timeout || 30 %></timeout> <% if @features.include?('ssl') %> <ssl_enabled>true</ssl_enabled> <% end %> </config>
注意:不要用 h()(Rails 辅助方法),Chef 的 ERB 环境里不可用;也不要依赖外部库,CGI 是 ruby 标准库,无需额外安装。
template 资源必须指定 cookbook 和 source
很多人写完 myapp.xml.erb 放进 templates/default/,却在资源里漏掉 cookbook 参数,导致 Chef 找不到模板文件,报错 Cannot find a template in cookbook xxx for node yyy。
完整写法必须包含:
-
source:模板文件名(含.erb后缀) -
cookbook:模板所在 cookbook 名(即使当前 cookbook 也要显式写) -
variables:传入哈希,键名即模板中@xxx变量名
示例资源定义:
template '/etc/myapp/config.xml' do source 'myapp.xml.erb' cookbook 'myapp-cookbook' variables( app_name: node['myapp']['name'], timeout: node['myapp']['timeout'], features: node['myapp']['features'] || [] ) mode '0644' owner 'root' group 'root' end
嵌套结构多时,优先用数组遍历而非硬编码节点
当 XML 需要动态生成多个同类子节点(如 <server></server> 列表),别写一堆 ...,用数组 + each 更可靠、易维护。
假设 node['myapp']['servers'] = [{ host: 'a.example.com', port: 8080 }, { host: 'b.example.com', port: 8081 }],模板应这样写:
<servers> <% @servers.each do |s| %> <server> <host><%= CGI.escapeHTML(s[:host]) %></host> <port><%= s[:port] %></port> </server> <% end %> </servers>
容易被忽略的是:数组元素若为 nil 或空哈希,each 会跳过,不会生成空节点;但如果数组本身是 nil,调用 each 会报错 undefined method `each' for nil:NilClass。所以建议在资源中预处理:servers: node['myapp']['servers'] || []。