You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Chef Inspec Ruby DSL调用自定义方法报未定义错误的解决咨询

问题:Chef InSpec中复用文件检查逻辑报错

需要编写多个Chef InSpec控制项,针对不同文件执行相同检查,且需通过only_if按需运行。为减少重复代码,定义了file_checks方法封装检查逻辑,但在control块中调用时出现undefined method 'file_checks'错误。移除only_if后问题依旧,相关代码如下:

File_attribs = Struct.new(:path, :mode, :owner, :group, :sha256sum)

def file_checks (file)
    describe "#{file.path}" do
        subject { file(file.path) }

        it "should exist" do
            expect(subject).to(exist)
        end
        it "should be a file" do
            expect(subject).to(be_file)
        end
        it "should have mode #{file.mode}" do
            expect(subject.mode).to(cmp file.mode)
        end
        it "should be owned by #{file.owner} user" do
            expect(subject.owner).to(eq file.owner)
        end
        it "should be owned by #{file.group} group" do
            expect(subject.group).to(eq file.group)
        end
        if ! file.sha256sum.nil?
            it "should match the known sha256 checksum" do
                expect(subject.sha256sum).to(eq file.sha256sum)
            end
        end
    end
end

control "test" do
    impact 0.7
    title "Test"
    desc "Test"

    test_files = [
        File_attribs.new("/etc/os-release", "0644", "root", "root", "fe133101dac304ceb134e47dea186e9d74d2a439cd32ae5452cc4f5b3c1eba0e")
    ]

    test_files.each do |test_file|
        file_checks test_file
    end
end

执行inspec exec命令时得到如下错误:

Profile:   tests from /hab/svc/core-tools/test/integration/default/core-tools.rb (tests from .hab.svc.core-tools.test.integration.default.core-tools.rb)
Version:   (not specified)
Target:    local://
Target ID: d98404f5-a6f9-5bfb-b3ac-3e75561c700f

  ×  test: Test
     ×  Control Source Code Error /hab/svc/core-tools/test/integration/default/core-tools.rb:30
     undefined method `file_checks' for #<Inspec::Rule:0x0000000006c0ee90 @impact=0.7, @title="Test", @descriptions={:default=>"Test"}, @refs=[], @tags={}, @resource_dsl=#<Module:0x0000000006c15cb8>, @__code=nil, @__block=#<Proc:0x0000000006c0ecd8 /hab/svc/core-tools/test/integration/default/core-tools.rb:30>, @__source_location={:ref=>"/hab/svc/core-tools/test/integration/default/core-tools.rb", :line=>30}, @__rule_id="test", @__profile_id="tests from .hab.svc.core-tools.test.integration.default.core-tools.rb", @__checks=[["describe", ["Control Source Code Error"], #<Proc:0x0000000006d24398 /hab/pkgs/chef/inspec/5.17.4/20220629103022/lib/gems/inspec-core-5.17.4/lib/inspec/rule.rb:407>]], @__skip_rule={}, @__merge_count=0, @__merge_changes=[], @__skip_only_if_eval=false, @__file="/hab/svc/core-tools/test/integration/default/core-tools.rb", @__group_title=nil>


Profile Summary: 0 successful controls, 1 control failure, 0 controls skipped
Test Summary: 0 successful, 1 failure, 0 skipped

尝试将结构体与file_checks整合为类时,又出现describe未定义的错误。请问是否可以在Chef InSpec控制项中这样定义方法?如何以最少的重复代码实现需求?


解决方案

原因说明

InSpec的control块运行在Inspec::Rule实例的上下文里,顶级定义的file_checks方法不在该实例的方法查找路径中,因此会报未定义错误。而将方法放到类中时,类的上下文没有InSpec DSL(如describe、it)的调用权限,所以会出现describe未定义的问题。

可行方案

方案1:单个control内复用(最简方式)

直接在control块内部定义file_checks方法,让方法处于Rule实例的上下文范围内:

control "test" do
  impact 0.7
  title "Test"
  desc "Test"

  File_attribs = Struct.new(:path, :mode, :owner, :group, :sha256sum)

  def file_checks(file)
    describe "#{file.path}" do
      subject { file(file.path) }

      it "should exist" do
        expect(subject).to(exist)
      end
      it "should be a file" do
        expect(subject).to(be_file)
      end
      it "should have mode #{file.mode}" do
        expect(subject.mode).to(cmp file.mode)
      end
      it "should be owned by #{file.owner} user" do
        expect(subject.owner).to(eq file.owner)
      end
      it "should be owned by #{file.group} group" do
        expect(subject.group).to(eq file.group)
      end
      unless file.sha256sum.nil?
        it "should match the known sha256 checksum" do
          expect(subject.sha256sum).to(eq file.sha256sum)
        end
      end
    end
  end

  test_files = [
    File_attribs.new("/etc/os-release", "0644", "root", "root", "fe133101dac304ceb134e47dea186e9d74d2a439cd32ae5452cc4f5b3c1eba0e")
  ]

  test_files.each do |test_file|
    file_checks test_file
    # 按需添加only_if条件示例
    # only_if { inspec.os.name == 'ubuntu' }
  end
end

方案2:跨control复用(扩展Rule DSL)

通过扩展Inspec::Rule的DSL模块,让file_checks方法能在所有control块中调用:

File_attribs = Struct.new(:path, :mode, :owner, :group, :sha256sum)

# 扩展InSpec的Rule DSL,让所有control都能调用该方法
Inspec::Rule.include(Module.new do
  def file_checks(file)
    describe "#{file.path}" do
      subject { file(file.path) }

      it "should exist" do
        expect(subject).to(exist)
      end
      it "should be a file" do
        expect(subject).to(be_file)
      end
      it "should have mode #{file.mode}" do
        expect(subject.mode).to(cmp file.mode)
      end
      it "should be owned by #{file.owner} user" do
        expect(subject.owner).to(eq file.owner)
      end
      it "should be owned by #{file.group} group" do
        expect(subject.group).to(eq file.group)
      end
      unless file.sha256sum.nil?
        it "should match the known sha256 checksum" do
          expect(subject.sha256sum).to(eq file.sha256sum)
        end
      end
    end
  end
end)

control "test" do
  impact 0.7
  title "Test"
  desc "Test"

  test_files = [
    File_attribs.new("/etc/os-release", "0644", "root", "root", "fe133101dac304ceb134e47dea186e9d74d2a439cd32ae5452cc4f5b3c1eba0e")
  ]

  test_files.each do |test_file|
    file_checks test_file
    # 按需添加only_if条件
    # only_if { 你的条件判断逻辑 }
  end
end

方案3:官方推荐方式(自定义资源)

创建自定义InSpec资源封装检查逻辑,扩展性更强,符合框架规范:

  1. 在profile的libraries/目录下创建file_checks.rb:
class FileChecks < Inspec.resource(1)
  name 'file_checks'
  desc '自定义资源:验证文件属性'

  def initialize(path, mode, owner, group, sha256sum = nil)
    @path = path
    @mode = mode
    @owner = owner
    @group = group
    @sha256sum = sha256sum
    @file = inspec.file(path)
  end

  def exists?
    @file.exist?
  end

  def is_file?
    @file.file?
  end

  def correct_mode?
    @file.mode.cmp(@mode)
  end

  def correct_owner?
    @file.owner == @owner
  end

  def correct_group?
    @file.group == @group
  end

  def correct_sha256?
    return true if @sha256sum.nil?
    @file.sha256sum == @sha256sum
  end
end
  1. 在控制文件中使用该自定义资源:
control "test" do
  impact 0.7
  title "Test"
  desc "Test"

  test_files = [
    { path: "/etc/os-release", mode: "0644", owner: "root", group: "root", sha256sum: "fe133101dac304ceb134e47dea186e9d74d2a439cd32ae5452cc4f5b3c1eba0e" }
  ]

  test_files.each do |test_file|
    describe file_checks(test_file[:path], test_file[:mode], test_file[:owner], test_file[:group], test_file[:sha256sum]) do
      it "should exist" do
        expect(subject).to exist
      end
      it "should be a file" do
        expect(subject.is_file?).to be true
      end
      it "should have correct mode" do
        expect(subject.correct_mode?).to be true
      end
      it "should have correct owner" do
        expect(subject.correct_owner?).to be true
      end
      it "should have correct group" do
        expect(subject.correct_group?).to be true
      end
      unless test_file[:sha256sum].nil?
        it "should have correct sha256sum" do
          expect(subject.correct_sha256?).to be true
        end
      end

      # 按需添加only_if条件
      # only_if { 你的条件判断逻辑 }
    end
  end
end

方案选择建议

  • 仅单个control需要复用:选方案1,实现最简单;
  • 多个control需要复用:选方案2或方案3,其中方案3是InSpec官方推荐的自定义资源方式,更利于后续维护和扩展。

内容的提问来源于stack exchange,提问作者Lauren Minnema

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 10:50:24