有人能向我解释一下 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 资源更好?
- 用户资源在各个平台上的工作方式相同 - 相同的方法可以在使用“useradd”以外的其他方式创建用户的操作系统上起作用。
- 负责此事的提供商知道如何检查用户是否已经存在,因此您不需要not_if。
- 相同的提供程序还可用于删除用户、锁定或解锁其密码以及更新现有用户帐户的其他属性。
但使用正确资源的最佳理由是它能更清楚地传达您的意图。您的目标可能不是运行一堆 shell 命令 - 而是确保系统中存在一些用户。用于执行此操作的命令只是一个实现细节。
提供商封装了这些细节,让我们可以自由地专注于描述我们想要的东西。