给出以下配方:
ruby_block "block1" do
block do
puts "in block1"
end
action :create
end
remote_file "/tmp/foo" do
puts "in remote_file"
source "https://yahoo.com"
end
我希望 ruby_block 首先运行(因为它先运行),然后运行 remote_file。
我想使用 ruby_block 来确定要从中下载 remote_file 的 url,因此顺序很重要。
如果不是我的 puts() 语句,我会假设它们按照预期的顺序运行,因为日志显示:
==> default: [2014-06-12T17:49:19+00:00] INFO: ruby_block[block1] called
==> default: [2014-06-12T17:49:19+00:00] INFO: remote_file[/tmp/foo] created file /tmp/foo
==> default: [2014-06-12T17:49:20+00:00] INFO: remote_file[/tmp/foo] updated file contents /tmp/foo
但除此之外,我的 puts() 语句如下所示:
==> default: in remote_file
==> default: in block1
如果您认为资源按照预期的顺序运行,请考虑以下方法:
ruby_block "block1" do
block do
node.default['test'] = {}
node.default['test']['foo'] ='https://google.com'
puts "in block1"
end
action :create
end
remote_file "/tmp/foo" do
puts "in remote_file"
source node.default['test']['foo']
end
这个失败如下:
==> default: [2014-06-12T17:55:38+00:00] ERROR: {} is not a valid `source` parameter for remote_file. `source` must be an absolute URI or an array of URIs.
==> default: [2014-06-12T17:55:38+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
字符串“in block1”没有出现在输出中,因此 ruby_block 从未运行。
所以问题是,我怎样才能强制 ruby_block 运行,并且先运行?
答案1
好问题——您的两个例子都按照我预期的方式工作,但原因并不明显。
正如 StephenKing 在他的回复中所写,首先要理解的是,配方被编译(以生成一组资源),然后资源被聚合(以对系统进行更改)。这两个阶段通常是交错的 - 在 Chef 完成所有配方的编译之前,您的一些资源可能会被聚合。Erik Hollensbe 在他的帖子中详细介绍了这一点“Chef 资源运行队列”。
这又是您的第一个例子:
ruby_block "block1" do
block do
puts "in block1"
end
action :create
end
remote_file "/tmp/foo" do
puts "in remote_file"
source "https://yahoo.com"
end
这些是 Chef 在处理该示例时将经历的步骤。
- 首先,编译 ruby_block 声明,从而将名为 的资源
ruby_block[block1]
添加到资源集合中。块的内容(第一个puts
语句)尚未运行 - 它被保存起来,以便在此资源收敛时运行。 - 接下来,编译 remote_file 声明。这将导致名为的资源
remote_file[/tmp/foo/]
被添加到资源集合中,其源为“https://yahoo.com“。在编译此声明的过程中,puts
将执行第二条语句 - 这会产生打印“in remote_file”的副作用,但不会影响放入资源集合中的资源。 - 由于没有其他内容需要编译,Chef 开始聚合资源集合中的资源。第一个是
ruby_block[block1]
,Chef 运行块中的 ruby 代码 - 打印“in block1”。运行完块后,它会记录一条消息,说明已调用该资源。 - 最后,Chef 收敛
remote_file[/tmp/foo]
。它再次记录与该活动相关的一条(或两条)消息。
这应该会产生以下输出序列:
- 编译 ruby_block 时没有打印任何内容。
- 在编译 remote_file 时将打印“in remote_file”。
- 当 ruby_block 收敛时,将会打印“in block1”。
- ruby_block 收敛后,将会打印一条 Chef 日志消息。
- 其他 Chef 日志消息将在 remote_file 聚合期间/之后打印。
回到第二个例子:
ruby_block "block1" do
block do
node.default['test'] = {}
node.default['test']['foo'] ='https://google.com'
puts "in block1"
end
action :create
end
remote_file "/tmp/foo" do
puts "in remote_file"
source node.default['test']['foo']
end
与第一个例子一样,我们不希望在编译 ruby_block 时打印任何内容 - 整个“块”被保存,并且其内容在该资源汇聚之前不会运行。
我们看到的第一个输出是“in remote_file”,因为该puts
语句是在 Chef 编译 remote_file 资源时执行的。在下一行,我们将参数设置source
为值node.default['test']['foo']
,显然是{}
。这不是有效的值source
,因此 Chef 运行在该点终止 - 在代码运行之前ruby_block
。
因此,该配方的预期输出是:
- 编译 ruby_block 时无输出
- 编译 remote_file 时打印“in remote_file”
source
由于参数无效而导致的错误
希望这能帮助您理解您所看到的行为,但我们仍有一个问题需要解决。
虽然你问“我怎样才能强制 ruby_block 首先运行?”,但你对 StephenKing 的评论表明这并不是你真正想要的 - 如果你真的希望该块首先运行,你可以将其直接放入你的配方代码中。或者,你可以使用.run_action() 方法强制资源在编译时立即收敛 - 但您说在 ruby_block 可用之前还有更多资源需要收敛。
正如我们上面所看到的,资源不是“运行”的,而是先“编译”然后“聚合”的。考虑到这一点,您需要的是让资源remote_file
使用一些在编译时未知但在聚合时已知的数据。换句话说,类似于“block”参数的东西ruby_block
——一段直到稍后才运行的代码。像这样:
remote_file "/tmp/foo" do
puts "in remote_file"
# this syntax isn't valid...
source do
node.default['test']['foo']
end
end
幸运的是,这样的事确实存在——它被称为惰性属性评估使用该功能,您的第二个示例将如下所示:
ruby_block "block1" do
block do
node.default['test'] = {}
node.default['test']['foo'] = 'https://google.com'
puts "in block1"
end
action :create
end
remote_file "/tmp/foo" do
puts "in remote_file"
source lazy { node['test']['foo'] }
end
这个菜谱的预期产量是多少?
- 编译 ruby_block 时无输出
- 编译 remote_file 时打印“in remote_file”
- 在收敛 ruby_block 时打印“in block1”
- Chef 日志消息显示 ruby_block 已聚合
- Chef 日志消息显示 remote_file 已聚合
答案2
厨师有一个编译和运行阶段。我假设 中的代码ruby_block
不会在编译阶段执行,因为它位于语句中block
(然后会在运行阶段执行)。但是,块puts
中的remote_file
位于资源定义中的“属性级别”,它实际上是由 chef 执行的(事实上,据我所知source node.default...
是一个函数调用)。
所以如果我理解正确的话,您想要做的事情可以用以下代码来完成:
node.default['test']['foo'] ='https://google.com'
remote_file "/tmp/foo" do
source node['test']['foo']
end
如果通过设置属性node.default
不起作用,请使用node.set
。
还要注意的是,我不会读取属性node.default[]
,而是直接读取node[]
。否则属性优先级厨师的功能毫无意义。