Chef Recipes - 在ruby_block中设置节点属性

6

我有一个用于多节点Web服务的Chef配方,每个节点都需要获取其他节点的主机名和IP地址,并将其放入自己的本地配置中。

下面是代码。问题在于,当在ruby_block中进行node.set[][]分配时,当依赖它们的模板被创建时,这些值为空。如果我要创建该模板,我必须将所有ruby_block代码移到外部,并将其“松散”放在配方中。这使得使用Chefspec等单元测试变得更加困难。

有没有任何Chef大师可以为我提供指导?在ruby_block内部像这样使用node.set[]只是不可能的吗?如果是这样,为什么文档中没有说明呢?

$cm = { :name => "web", :hostname => "" , :ip_addr => "" }
$ca = { :name => "data", :hostname => "" , :ip_addr => "" }
$cg = { :name => "gateway", :hostname => "" , :ip_addr => "" }
$component_list = [$cm, $ca, $cg]

ruby_block "get host addresses" do
  block do
    for cmpnt in $component_list
       # do REST calls to external service to get cmpnt.hostname, ip_addr
       # .......
       node.set[cmpnt.name]['name'] = cmpnt.name
       node.set[cmpnt.name]['host'] = cmpnt.hostname
       node.set[cmpnt.name]['ip'] = cmpnt.ip_addr   
    end
  end
end

template "/etc/app/configuration/config.xml" do
  source "config.xml.erb"
  variables( :dataHost => node['data']['host'],
       :webHost =>  node['web']['host'],
       :gatewayHost =>  node['gateway']['host'] )
  action :create
end

我还添加了。
  subscribes  :create, "ruby_block[get host addresses]", :immediately

为了确保在创建模板之前运行ruby_block,可以将ruby_block定义放到模板定义之前。但是这并没有什么区别。

4个回答

5

我知道这是一篇旧文章,但是作为参考,我刚刚找到了这个代码片段,在编译和收敛阶段中给出了变量赋值的一个很好的示例。为了使这个代码适应你的例子,你需要在你的ruby_block中添加以下代码:

       template_r = run_context.resource_collection.find(:template => "/etc/app/configuration/config.xml")

       template_r.content node['data']['host']
       template_r.content node['web']['host']
       template_r.content node['gateway']['host']

对于Chef 11,还请参阅惰性属性评估

2
问题似乎是在模板资源定义内部的属性值在实际调用任何资源之前就被评估了。也就是说,文件首先作为简单的 Ruby 执行,编译资源,然后才会调用资源操作。到那时,为时已晚。当我尝试将某些属性操作封装到资源中时,我遇到了同样的问题。它根本不起作用。如果有人知道这个问题的解决方案,我会非常感激。
b = ruby_block...
...
end
b.run_action(:create)

可能会有用。它立即调用资源。


1

最简单的解决方法是不使用Chef属性,也不使用ruby_block来处理与REST API的通信。代码也可以移动到自定义资源中以实现更好的重用:

unified_mode true
provides :my_resource

action :run do
  cm = { :name => "web", :hostname => "" , :ip_addr => "" }
  ca = { :name => "data", :hostname => "" , :ip_addr => "" }
  cg = { :name => "gateway", :hostname => "" , :ip_addr => "" }
  component_list = [cm, ca, cg]

  hash = {}

  for cmpnt in component_list
     # do REST calls to external service to get cmpnt.hostname, ip_addr
     # .......
     hash[cmpnt.name] = {}
     hash[cmpnt.name]['name'] = cmpnt.name
     hash[cmpnt.name]['host'] = cmpnt.hostname
     hash[cmpnt.name]['ip'] = cmpnt.ip_addr   
  end

  template "/etc/app/configuration/config.xml" do
    source "config.xml.erb"
    variables( :dataHost => hash['data']['host'],
       :webHost =>  hash['web']['host'],
       :gatewayHost =>  hash['gateway']['host'] )
    action :create
  end
end

通过使用统一模式并移动到自定义资源中,还可以更轻松地使用节点属性,而无需使用lazy {}或ruby_blocks。它还允许在调用此代码之前进行Chef配置(例如设置resolv.conf或其他网络要求),而不必考虑在配方上下文中的编译/汇总两个传递问题。
此外,没有理由使用像ruby_block这样的资源来执行纯ruby处理,因为它不会改变受管理的系统。在这种情况下,ruby_block仅命中REST服务以收集数据。那不需要放入Chef资源中。从问题中并不清楚是否这样做是因为提问者认为这是“最佳实践”(在这种情况下不是),还是因为它被用于将执行移动到编译时以允许其他不属于问题的chef资源首先触发(在这种情况下,使用自定义资源比使用ruby_block更好)。

0

虽然这个问题已经有一段时间了,但如果仍有人在寻找答案,惰性求值是你的好朋友:

template '/tmp/sql_file.sql' do
  source "sql_file.sql.erb"
  mode 0700
  variables lazy {

    # Create a new instance of MySQL library
    mysql_lib = Acx::MySQL.new(
      '127.0.0.1', 'root', node['mysql']['service']['pass']
    )
    password = node['mysql']['service']['support_admin']['ct_password']

    # It returns the encrypted password after evaluate it, to 
    # be used in template variables
    { admin_password:  mysql_lib.encrypted_password(password) }
  }
end

https://docs.chef.io/resource_common.html#lazy-evaluation


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接