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

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

有人能向我解释一下 Chef 的工作原理吗?这是一个相当广泛的问题,因此为了缩小范围,我有一个非常简单的配方,它循环遍历用户列表,如果用户尚不存在,则创建每个用户。它不起作用。

据我所知,循环似​​乎按照我的预期进行。循环完成后,我创建每个用户的 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

我正在使用 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

配方中的 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

答案1

使您的脚本名称独一无二......

bash "create_user_#{user}" do

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

答案2

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

在“编译”阶段,Chef 客户端会运行配方中的代码来构建资源收集。这是资源您已告知 Chef 在您的系统上进行管理,以及它们的目标状态。例如,要说明目录资源/tmp/foo应存在,并由 root 拥有:

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

在“聚合”阶段,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

但是,在这种特殊情况下,使用 Chef 的用户资源会更好。以下是您的食谱的替代品(没有调试消息):

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

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

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

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

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

相关内容