【问题标题】:Chef use cookbook_file in ruby block厨师在 ruby​​ 块中使用 cookbook_file
【发布时间】:2026-02-02 13:30:02
【问题描述】:

我有以下代码来确定 Java 在盒子上的位置。 Java 随我们的应用程序一起提供,应用程序中包含的 Java 版本有所不同。

def app_java_home
  if Dir.exist?("#{app_home}/jre-server/linux")
    Dir.chdir("#{app_home}/jre-server/linux") do
      Dir.glob('jdk*').select { |f| File.directory? f }[0]
    end
  end
end

然后,在我的食谱中,我有

aws_s3_file "#{app_download_path}/#{app_s3['archive_file']}" do
  bucket app_s3['bucket']
  remote_path app_s3['remote_path']
  region aws_region
  not_if { ::Dir.exists?(app_bin_dir) }
  not_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end

execute 'extract' do
  user 'root'
  command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
  not_if { ::Dir.exists?("#{app_home}/ourapp") }
  only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end

execute 'move' do
  user 'root'
  command "mv #{app_download_path}/ourapp/ #{app_install_path}"
  not_if { ::Dir.exists?(app_home) }
end

cookbook_file "#{app_java_home}/jre/lib/security/local_policy.jar" do
  source %W[#{app_release}/local_policy.jar default/local_policy.jar]
  owner app_user_name
  group app_group_name
  mode 0755
end

cookbook_file "#{app_java_home}/jre/lib/security/US_export_policy.jar" do
  source %W[#{app_release}/US_export_policy.jar default/US_export_policy.jar]
  owner app_user_name
  group app_group_name
  mode 0755
end

但是cookbook_file这两个资源因为找不到目录而失败:

No such file or directory @ dir_chdir - /ourapp/jre-server/linux/

经过大量谷歌搜索,我得出的结论是,配方的编译时间和运行时间之间存在 ..“不匹配”(?)。基本上,如果我理解正确,它会首先尝试运行 cookbook_file 资源但失败。所以永远不要下载、解压和安装应用程序。

我尝试在目录存在时运行app_java_home,它似乎确实按我想要的方式工作..

我尝试将cookbook_file 资源放在ruby_block 中,但后来却得到:

undefined method `cookbook_file' for Chef::Resource::RubyBlock

app_java_home .. 函数 (?) 过去看起来像这样:

def app_java_home
  "#{app_home}/jre-server/linux/#{jdk_version}"
end

jdk_version 来自数据包。这工作得很好,但是我们的系统中有一个长期存在的错误/功能请求,有时会发生“他们”得到他们放入数据包中的版本错误,导致各种问题.. 所以他们想要一种方法来删除这个依赖,而是动态地“解决这个问题”。

Ruby 和 Chef 不是我的强项,所以我不确定接下来要尝试什么。我找到了对Chef::Resources::CookbookFile 的引用(如果我理解的话,可以/应该在ruby_blocks 中使用它),但找不到任何关于它的示例或文档。 RubyDocs 上的链接已损坏。

【问题讨论】:

  • app_java_home 是食谱的一部分吗?你是如何运行它的?
  • 我将该代码放入 Ruby shell 并运行它。在创建目录之前和之后,它都可以工作。当它出现在说明书(libraries/attributes.rb)中时,问题是它似乎“在错误的时间”运行。
  • 无法从问题中发布的简洁错误中看出,但@ dir_chdir 似乎在app_java_home 方法中表明Dir.chdir 存在问题。 cookbook_file 资源不需要更改目录。 #{app_home} 是由配方中的代码创建的吗?
  • 没错。问题是cookbook_fileaws_s3_file 和两个execute 之前运行之前 解压并安装应用程序。所以当cookbook_file运行时,那个目录还不存在。
  • 好的。这就是当您运行配方时会发生的情况 - app_java_home home 在 compile 时间内运行以获得返回值。配方中声明的所有资源都将在 converge 阶段并按照它们定义的顺序运行。只有在aws_s3_file之上有cookbook_file资源,它才会在它之前运行,否则不会。

标签: ruby chef-infra cookbook


【解决方案1】:

在此处添加答案以获得更好的解释。

  • 任何不在任何 Chef 资源中的 (Ruby) 代码都将在编译阶段运行

  • 所有资源声明都将按照定义的顺序在收敛阶段运行

幸运的是,如果需要,有一种方法可以让资源在编译阶段运行。虽然恕我直言,但在特殊情况下应该谨慎行事。

根据您的评论,aws_s3_fileexecute 资源是解压缩应用程序(并创建目录)的资源。在这种情况下,您似乎希望它们在编译阶段运行。


Chef 客户端 16.0 之前的版本

run_action 选项与应在编译时执行的操作一起使用。例如execute资源采取行动:run

# Note action ":nothing" and "run_action"

execute 'extract' do
  user 'root'
  command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
  not_if { ::Dir.exists?("#{app_home}/ourapp") }
  only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
  action :nothing
end.run_action(:run)

Chef 客户端 16.0 及以上版本

我们可以在资源中添加common propertyexecute 资源示例:

# Note the extra property "compile_time"

execute 'extract' do
  user 'root'
  command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
  not_if { ::Dir.exists?("#{app_home}/ourapp") }
  only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
  compile_time true
end

最后回答问题的主题:

厨师在 ruby​​ 块中使用 cookbook_file

这是不可能的。请参阅顶部的第一点。如果我们希望 Ruby 代码在收敛期间运行(而不是编译),我们将其放在 ruby_block 资源中。所以它可以包含如下代码(例如):

ruby_block 'get directory' do
  block do
    def app_java_home
      "#{app_home}/jre-server/linux/#{jdk_version}"
    end
  end
end

【讨论】:

  • 如果我将compile_time true 放在两个executeaws_s3_file 中,我会得到undefined method compile_time' 用于来自cookbook aws. But taking compile_time 的自定义资源aws_s3_file 作为提示,我颠倒了这个建议。将compile_time false 放在两个cookbook_file 资源中。不幸的是,这给出了同样的错误 - undefined method。但不幸的是,即使按照你的建议完全,我也会得到undefined method for Chef::Resource::Execute!
  • 但是,这些测试表明我实际上希望所有这些都在 run 阶段运行,而不是在编译阶段运行。但是,我不知道该怎么做。
  • aws_s3_file 自定义资源可能不支持此属性,但其他内置 Chef 资源应该支持。 compile_time false 通常是隐含的,是资源的默认行为。我在execute 资源中使用了compile_time true,它不会像undefined_method 那样给出错误。
  • 该选项是何时添加的?我以为我正在运行一个相当新的版本 - v12.13.37。
  • 因此,如果您注意到,run_action 会采取该资源支持的操作。 :runexecute 资源支持。根据aws cookbookaws_s3_file 的有效操作是:createcreate_if_missingdeletetouch。使用其中之一。
【解决方案2】:

在@seshadri_c 的帮助下,我终于设法解决了这个问题!花了一些时间,因为我一直误解建议等。

所以这就是我想出的(为了后代):

def jdk_version(required = true)
  base_dir = "#{app_home}/jre-server/linux"
  if Dir.exist?("#{base_dir}")
    Dir.chdir("#{app_home}/jre-server/linux") do
      Dir.glob("jdk*").each do |f|
        if File.directory?(f)
          return "#{f}"
        end
      end
    end
  end
end

def app_java_home
  return "#{app_home}/jre-server/linux/#{jdk_version}"
end

原来我也只需要单独获取版本,所以我重新安排了一些功能。我敢肯定它可以写得更干净,但这里的诀窍是使用return 而不是puts/print!好吧,我是一名程序员,但不是 Ruby 程序员,所以不知道这是一个选择..

然后,在食谱中,我在需要的地方添加了.run_action()cookbook_file 不需要它们,这稍微简化了一些事情:

aws_s3_file "#{app_download_path}/#{app_s3['archive_file']}" do
  bucket app_s3['bucket']
  remote_path app_s3['remote_path']
  region aws_region
  not_if { ::Dir.exists?(app_bin_dir) }
  not_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end.run_action(:create)

execute 'extract' do
  user 'root'
  command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
  not_if { ::Dir.exists?("#{app_home}/app") }
  only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end.run_action(:run)

execute 'move' do
  user 'root'
  command "mv #{app_download_path}/app/ #{app_install_path}"
  not_if { ::Dir.exists?(app_home) }
end.run_action(:run)


#  JCE Unlimited Strength Jurisdiction Policy Files
cookbook_file "#{app_java_home}/jre/lib/security/local_policy.jar" do
  source %W[#{app_release}/local_policy.jar default/local_policy.jar]
  owner app_user_name
  group app_group_name
  mode 0755
end

cookbook_file "#{app_java_home}/jre/lib/security/US_export_policy.jar" do
  source %W[#{app_release}/US_export_policy.jar default/US_export_policy.jar]
  owner app_user_name
  group app_group_name
  mode 0755
end

有了这一切,一切都在按预期运行,一切似乎都在工作。

【讨论】:

  • 很高兴您找到了解决方案!简单说明一下,return 关键字在 Ruby 方法中是可选的,可以在末尾指定任意值,默认返回。