在 Chef (solo) 食谱中循环的正确方法是什么?

Mat*_*son 8 chef chef-solo

有人可以向我解释厨师是如何工作的吗?这是一个相当广泛的问题,所以为了缩小范围,我有一个非常简单的方法,它循环遍历用户列表并创建每个用户(如果它们尚不存在)。这是行不通的。

据我所知,循环似​​乎正如我所期望的那样发生。一旦循环完成了我创建每个用户的 bash 命令,循环中的每次迭代都会执行一次。然而,当 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
Run Code Online (Sandbox Code Playgroud)

我正在使用具有以下设置的 Vagrant 对此进行测试:

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
Run Code Online (Sandbox Code Playgroud)

配方中的 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
Run Code Online (Sandbox Code Playgroud)

zts*_*zts 10

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

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

directory "/tmp/foo" do
  owner "root"
end
Run Code Online (Sandbox Code Playgroud)

在“收敛”阶段,Chef 客户端使用提供程序加载每个资源的当前状态,然后将其与目标状态进行比较。如果它们不同,Chef 将更新系统。对于我们的目录资源,如果目录不存在,Chef 将创建该目录,并在必要时将其所有者更改为“root”。

资源由它们的名称和类型唯一标识 - 我们的目录将是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
Run Code Online (Sandbox Code Playgroud)

但是,在这种特殊情况下,您将受益于使用 Chef 的用户资源。这是您的配方的替代品(没有调试消息):

node[:users].each do |u|
  user u['username'] do
    action :create
  end
end
Run Code Online (Sandbox Code Playgroud)

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

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

但使用正确资源的最佳理由是它更清楚地传达了您的意图。您的目标可能不是运行一堆 shell 命令——而是确保某些用户存在于您的系统上。用于执行此操作的命令只是一个实现细节。

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


小智 5

使您的脚本名称唯一...

bash "create_user_#{user}" do

FWIW,我已经多次使用https://github.com/fnichol/chef-user,它允许您根据属性和数据包创建/删除用户。