如何使用Chef懒惰地评估任意变量

我正在编写一个Chef配方来安装我们的应用程序代码并执行它。 配方需要特别关注此代码最终的目录(用于运行模板,设置日志转发等)。 因此,目录本身会在不同的食谱中出现在很多地方。

我试图获取/定义一个变量,所以我可以在我的资源块中使用字符串插值重新使用它。 这非常简单:

home = node['etc']['passwd'][node['nodejs']['user']]['dir'] 

一个示例用法是运行npm install同时告诉它在主目录中插入repo下载,如下所示:

 execute "npm install" do command "npm install #{prefix}#{app} --prefix #{home}" end 

除了定义home变量的第一个块将在编译时运行。 在新的服务器上,我的nodejs用户帐户可能尚不存在,这是一个问题,给出了一个

 NoMethodError undefined method '[]' for nil:NilClass 

我有一些解决方法,但我想要一个特定的解决方案,使home变量只在配方执行时获取,而不是编译时。


解决方法1

动态评估ruby块内的home变量,如下所示:

 ruby_block "fetch home dir" do block do home = node['etc']['passwd'][node['nodejs']['user']]['dir'] end end 

这似乎没有实际效果,当你尝试做类似这样的事情时, 为Chef :: Resource :: Directory提供一个NoMethodError未定义方法的主页

 directory ".npm" do path "#{home}/.npm" end 

我觉得我必须在这里做错事。

解决方法2

懒惰地评估每个需要它的资源的参数。 所以改为:

 directory ".npm" do path lazy "#{node['etc']['passwd'][node['nodejs']['user']]['dir']}/.npm" end 

但是,只需要维护一行代码,将其存储在变量中并完成它就会非常棒。

解决方法3

在编译时创建用户。 这当然有效,使用这里链接的通知技巧 ,如下所示:

 u = user node['nodejs']['user'] do comment "The #{node['nodejs']['user']} is the user we want all our nodejs apps will run under." username node['nodejs']['user'] home "/home/#{node['nodejs']['user']}" end u.run_action(:create) 

这完全解决了我的问题,但是在其他情况下我可以想象想要延迟评估变量的能力,所以我让我的问题成立。

我想要什么

我真的希望能够做到

 home lazy = node['etc']['passwd'][node['nodejs']['user']]['dir'] 

但这不是合法的语法,因为NameError无法在ubuntu版本13.10上找到home的资源 (这是一个奇怪的语法错误,但无论如何)。 有没有合法的方法来实现这一目标?

我没有测试过这个特殊的代码,但我在cookbook中做了类似的事情,并使用lambda来延迟评估,如下所示:

 home = lambda {node['etc']['passwd'][node['nodejs']['user']]['dir']} execute "npm install" do command "npm install #{prefix}#{app} --prefix #{home.call}" end 

对于ruby_block ,块中的任何变量都需要是全局的,因为块中定义的任何变量都是本地的。

你不能在库中使用lambda来延迟执行,因此在这种情况下ruby_block运行良好。

@thoughtcroft回答不适合我的主厨客户12.8.1

在这种情况下,我将所需的代码放入自定义资源中,并使用惰性属性调用它。

 mycookbook_myresource "name" do attribute lazy { myvar } end 

不是优雅的解决方案,但它适合我。