什么是正确的方式来循环厨师(独奏)配方?

有人能向我解释一下厨师的工作原理吗? 这是一个相当广泛的问题,所以要缩小它的范围,我有这个非常简单的配方,循环用户列表,并创build每个用户,如果他们不存在。 这是行不通的。

从我可以告诉循环似乎正在发生,如我所料。 一旦循环已经完成我的bash命令来创build每个用户执行,每循环一次迭代。 但是,执行bash命令时,它们似乎只有第一个循环迭代中的用户值。

编写一个类似于这个例子的可变数据循环的配方的正确方法是什么?

这是配方:

node[:users].each do |user| puts "in loop for #{user['username']}" bash "create_user" do user "root" code do puts "running 'useradd' for #{user['username']}" "useradd #{user['username']}" end not_if do puts "checking /etc/passwd for #{user['username']}" "cat /etc/passwd | grep #{user['username']}" end end end 

我使用以下设置使用Vagranttesting:

 Vagrant::Config.run do |config| config.vm.box = "precise32" config.vm.box_url = "http://files.vagrantup.com/precise32.box" config.vm.provision :chef_solo do |chef| chef.add_recipe "sample" chef.json = { :users => [ {:username => 'testA'}, {:username => 'testB'}, {:username => 'testC'}, {:username => 'testD'}, {:username => 'testE'}, ], } end end 

puts语句在配方中生成的消息如下所示:

 2013-03-08T01:03:46+00:00] INFO: Start handlers complete. in loop for testA in loop for testB in loop for testC in loop for testD in loop for testE [2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5) checking /etc/passwd for testA [2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5) checking /etc/passwd for testA [2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5) checking /etc/passwd for testA [2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5) checking /etc/passwd for testA [2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5) checking /etc/passwd for testA [2013-03-08T01:03:46+00:00] INFO: Chef Run complete in 0.026071 seconds 

使您的脚本名称独特…

bash "create_user_#{user}" do

FWIW,我已经多次使用https://github.com/fnichol/chef-user ,它允许你创build/删除基于属性和数据库的用户。

您所看到的行为可以通过了解Chef客户端运行的两个关键阶段(编译和收敛)之间的差异来解释。

在“编译”阶段,Chef客户端运行食谱中的代码来构build资源集合 。 这是您告诉Chef在您的系统上pipe理的资源以及目标状态的列表。 例如,一个目录资源说/tmp/foo应该存在,并由root拥有:

 directory "/tmp/foo" do owner "root" end 

在“融合”阶段,Chef客户端使用提供者加载每个资源的当前状态,然后将其与目标状态进行比较。 如果不同,厨师会更新系统。 对于我们的Directory资源,Chef会创build一个不存在的目录,如有必要,将其所有者改为“root”。

资源是由它们的名称和types唯一标识的 – 我们的目录将是directory[/tmp/foo] 。 当你有两个具有相同名称但属性不同的资源时会发生奇怪的事情 – 这就解释了你的问题,可以用Darrin Holst的答案来解决:

 node[:users].each do |user| puts "in loop for #{user['username']}" bash "create_user_#{user}" do user "root" code do puts "running 'useradd' for #{user['username']}" "useradd #{user['username']}" end not_if do puts "checking /etc/passwd for #{user['username']}" "cat /etc/passwd | grep #{user['username']}" end end end 

但是,在这种特殊情况下,您将从使用厨师的用户资源中受益。 这里是你的配方replace(没有debugging信息):

 node[:users].each do |u| user u['username'] do action :create end end 

为什么这比一组bash资源更好?

  1. 用户资源在各种平台上的工作方式相同 – 相同的配方将在使用“useradd”之外的操作系统来创build用户。
  2. 负责这个的提供者知道如何检查用户是否已经存在,所以你不需要not_if。
  3. 也可以使用相同的提供程序来删除用户,locking或解锁其密码,并更新现有用户帐户的其他属性。

但使用正确的资源的最好的理由是它更清楚地传达你的意图。 你的目标可能不是运行一堆shell命令 – 这是为了确保你的系统上有一些用户。 用来做这些的命令只是一个实现细节。

提供者封装了这些细节,让我们可以自由地专注于描述我们想要的东西。