如何使用include_recipe检查是否包含配方?

时间:2014-04-04 14:38:00

标签: ruby chef chef-recipe chef-solo

我读到了Chef wrapper cookbook,现在我试着在角色烹饪书中重构一些厨房角色。例如,我有一个灯光角色食谱,它做了类似的事情:

# This is coming from another cookbook
include_recipe 'mysql_role::default'
# Install and tune apache2
include_recipe 'lamp_role::apache2'
# Install php and usual modules
include_recipe 'lamp_role::php'
include_recipe 'lamp_role::php_modules'

包含的食谱可能有一些应用程序逻辑,但它们通常以包含官方烹饪书结束

# in lamp_role::php recipe
include_recipe 'php'

现在在另一个食谱中,我想检查官方php::default食谱是否已被包含在某处,我曾经使用以下两种语法中的一种来做到这一点:

# This is true for recipes included both in roles and in run_list
include_recipe 'newrelic::php_agent' if node.recipes.include?('php')
# This works only for runlist recipes
include_recipe 'newrelic::php_agent' if node.recipe?('php')

有没有办法检查某个配方是否已包含在其他地方的include_recipe

P.S。我正在使用chef-solo进行部署。

用例

我写了一个监控配方,安装monit并添加一些模板,这里是一段代码

# Install and configure monit
include_recipe 'monit'
# Template shouldn't be installed if apache is not present
monitrc 'apache2' do
  template_cookbook 'monitoring_role'
  # My previous attempt, which doesn't work if recipe is included with include_recipe
  only_if { node.recipes.include?('apache2') || node[:monit][:templates].include?('apache2') }
end

我添加了一个属性(node[:monit][:templates])来手动指定节点属性中的monit模板,但很容易忘记它们。显然我不希望所有主机中都包含apache2配方,但是我在多台机器中使用这个配方并且我要求检查我可以在所有主机上重复使用配方,并且将安装monit当前节点中存在的守护进程的模板。

3 个答案:

答案 0 :(得分:4)

当然,您可以检查run_context。这就是ChefSpec的作用:

#
# Automatically appends "+::default+" to recipes that need them.
#
# @param [String] name
#
# @return [String]
#
def with_default(name)
  name.include?('::') ? name : "#{name}::default"
end

#
# The list of loaded recipes on the Chef run (normalized)
#
# @return [Array<String>]
#
def loaded_recipes
  @runner.run_context.loaded_recipes.map { |name| with_default(name) }
end

这样就回答了你的问题。现在我的问题 - 为什么?

Chef将自动为您构建依赖关系图。它只会在适当的订购地点包含一次配方。您不需要手动管理此图表,它就是“好的”#34;不止一次地包括相同的食谱。

事实上,在内部,我们经常使用这种模式。每个食谱都会在最顶层声明它所依赖的食谱。我建议观看此视频:https://www.youtube.com/watch?v=hYt0E84kYUI

答案 1 :(得分:1)

最后的强硬

sethvargo answer是正确的,我可以使用以下代码在我的cookbook的库文件夹中添加一个ruby文件,以获得我要求的方法

class Chef

  class Node
    def has_recipe?(recipe_name)
      loaded_recipes.include?(with_default(recipe_name))
    end

    private
    #
    # Automatically appends "+::default+" to recipes that need them.
    #
    # @param [String] name
    #
    # @return [String]
    #
    def with_default(name)
      name.include?('::') ? name : "#{name}::default"
    end

    #
    # The list of loaded recipes on the Chef run (normalized)
    #
    # @return [Array<String>]
    #
    def loaded_recipes
      node.run_context.loaded_recipes.map { |name| with_default(name) }
    end

  end

end

上面我用例中的示例已被删除,因为monitrc是一个定义,因此不接受only_if/not_if块。所以我需要使用普通的ruby重写那段代码,它在配方编译时 (see this)进行评估。这是修改后的代码

if node.has_recipe?('apache2') # This if is evaluated when recipes are loaded
  monitrc 'apache2' do
    template_cookbook 'monitoring_role'  
  end
end

重要的是这段代码可能会有不一致的行为。这取决于何时包含检查的配方。如果我将此食谱称为monitoring::default并且我有一个名为lamp::default的食谱调用include_recipe 'apache2',则在以下情况下安装模板

run_list = ['lamp::default', 'monitoring::default']

使用此运行列表时,调用将被评估为false

run_list = ['monitoring::default', 'lamp::default']

has_recipe?方法适用于执行时间保护,即

package('foo') do
  only_if { node.has_recipe?('bar') }
end

适用于所有情况。

答案 2 :(得分:0)

chef node中还有一个recipe?可用方法,用于检查节点run_list中是否包含配方