Chef如何使用模板生成动态XML

1次阅读

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

Chef如何使用模板生成动态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 环境里不可用;也不要依赖外部库,CGIruby 标准库,无需额外安装。

template 资源必须指定 cookbooksource

很多人写完 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'] || []

text=ZqhQzanResources