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

时间:2013-04-04 16:05:23

标签: ruby scope dsl chef

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

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

任何厨师大师都可以让我直截了当吗?在ruby_block中这样做node.set []是不是不可能吗?如果是这样,为什么不在docs?

中这样说
$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。这没有什么区别。

4 个答案:

答案 0 :(得分:5)

我意识到这是一篇旧文章,但是为了将来的参考,我只是遇到了this gist,这给出了Compile vs. Converge阶段中节点变量赋值的一个很好的例子。要根据您的示例调整要点,您需要在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,请参阅Lazy Attribute Evaluation

答案 1 :(得分:2)

问题似乎是在实际调用任何资源之前,会对模板资源定义中的属性值进行评估。 即该文件首先作为简单的Ruby执行,编译资源,只调用资源操作。到那时,已经太晚了。 在尝试将某些属性操作封装到资源中时遇到了同样的问题。它根本不起作用。如果有人知道这个问题的解决方案,我会非常感激。

编辑:

b = ruby_block...
...
end
b.run_action(:create)

可能会做到这一点。它会立即调用资源。

答案 2 :(得分: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

通过使用Unified_mode并转移到自定义资源中,还可以更轻松地使用节点属性,而无需使用惰性{}或ruby_blocks。它还可以在调用此代码之前允许进行厨师配置(例如在执行REST调用之前设置resolv.conf或其他网络要求),而不必考虑在配方上下文中编译/收敛两次通过问题。

也没有理由使用诸如ruby_block之类的资源来进行纯红宝石处理,这不会改变管理下的系统。在这种情况下,ruby_block纯粹是通过点击REST服务来收集数据。不需要将其放置在Chef资源中。从问题中尚不清楚是否这样做是因为发问者虽然是“最佳实践”(在这种情况下不是),或者是否正在执行以将执行移至编译时以允许其他首先不触发问题的厨师资源(在这种情况下,使用自定义资源比使用ruby_block更好。)

答案 3 :(得分: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