mirror of
https://github.com/inspec/inspec
synced 2024-11-23 21:23:29 +00:00
Merge pull request #780 from chef/dr/lib-loading
support intra-libraries file referencing + loading
This commit is contained in:
commit
e4e29050bb
9 changed files with 152 additions and 6 deletions
|
@ -4,6 +4,7 @@
|
|||
|
||||
require 'inspec/rule'
|
||||
require 'inspec/dsl'
|
||||
require 'inspec/require_loader'
|
||||
require 'securerandom'
|
||||
|
||||
module Inspec
|
||||
|
@ -19,6 +20,7 @@ module Inspec
|
|||
@backend = backend
|
||||
@conf = conf.dup
|
||||
@rules = {}
|
||||
@require_loader = ::Inspec::RequireLoader.new
|
||||
|
||||
reload_dsl
|
||||
end
|
||||
|
@ -26,7 +28,30 @@ module Inspec
|
|||
def reload_dsl
|
||||
resources_dsl = Inspec::Resource.create_dsl(@backend)
|
||||
ctx = create_context(resources_dsl, rule_context(resources_dsl))
|
||||
@profile_context = ctx.new(@backend, @conf)
|
||||
@profile_context = ctx.new(@backend, @conf, @require_loader)
|
||||
end
|
||||
|
||||
def load_libraries(libs)
|
||||
lib_prefix = 'libraries' + File::SEPARATOR
|
||||
autoloads = []
|
||||
|
||||
libs.each do |content, source, line|
|
||||
path = source
|
||||
if source.start_with?(lib_prefix)
|
||||
path = source.sub(lib_prefix, '')
|
||||
autoloads.push(path) if File.dirname(path) == '.'
|
||||
end
|
||||
|
||||
@require_loader.add(path, content, source, line)
|
||||
end
|
||||
|
||||
# load all files directly that are flat inside the libraries folder
|
||||
autoloads.each do |path|
|
||||
next unless path.end_with?('.rb')
|
||||
load(*@require_loader.load(path)) unless @require_loader.loaded?(path)
|
||||
end
|
||||
|
||||
reload_dsl
|
||||
end
|
||||
|
||||
def load(content, source = nil, line = nil)
|
||||
|
@ -100,12 +125,29 @@ module Inspec
|
|||
include Inspec::DSL
|
||||
include resources_dsl
|
||||
|
||||
def initialize(backend, conf) # rubocop:disable Lint/NestedMethodDefinition, Lint/DuplicateMethods
|
||||
def initialize(backend, conf, require_loader) # rubocop:disable Lint/NestedMethodDefinition, Lint/DuplicateMethods
|
||||
@backend = backend
|
||||
@conf = conf
|
||||
@require_loader = require_loader
|
||||
@skip_profile = false
|
||||
end
|
||||
|
||||
# Save the toplevel require method to load all ruby dependencies.
|
||||
# It is used whenever the `require 'lib'` is not in libraries.
|
||||
alias_method :__ruby_require, :require
|
||||
|
||||
def require(path)
|
||||
rbpath = path + '.rb'
|
||||
return __ruby_require(path) if !@require_loader.exists?(rbpath)
|
||||
return false if @require_loader.loaded?(rbpath)
|
||||
|
||||
# This is equivalent to calling `require 'lib'` with lib on disk.
|
||||
# We cannot rely on libraries residing on disk however.
|
||||
# TODO: Sandboxing.
|
||||
content, path, line = @require_loader.load(rbpath)
|
||||
eval(content, TOPLEVEL_BINDING, path, line) # rubocop:disable Lint/Eval
|
||||
end
|
||||
|
||||
define_method :title do |arg|
|
||||
profile_context_owner.set_header(:title, arg)
|
||||
end
|
||||
|
|
33
lib/inspec/require_loader.rb
Normal file
33
lib/inspec/require_loader.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# encoding: utf-8
|
||||
# author: Dominik Richter
|
||||
# author: Christoph Hartmann
|
||||
|
||||
module Inspec
|
||||
class RequireLoader
|
||||
Item = Struct.new(:content, :ref, :line, :loaded)
|
||||
|
||||
def initialize
|
||||
@contents = {}
|
||||
end
|
||||
|
||||
def add(path, content, ref, line)
|
||||
@contents[path] = Item.new(content, ref, line, false)
|
||||
end
|
||||
|
||||
def load(path)
|
||||
c = @contents[path]
|
||||
c.loaded = true
|
||||
res = [c.content, c.ref, c.line || 1]
|
||||
yield res if block_given?
|
||||
res
|
||||
end
|
||||
|
||||
def exists?(path)
|
||||
@contents.key?(path)
|
||||
end
|
||||
|
||||
def loaded?(path)
|
||||
@contents[path].loaded == true
|
||||
end
|
||||
end
|
||||
end
|
|
@ -99,10 +99,7 @@ module Inspec
|
|||
|
||||
# load all libraries
|
||||
ctx = create_context(options)
|
||||
libs.each do |lib|
|
||||
ctx.load(lib[:content].to_s, lib[:ref], lib[:line] || 1)
|
||||
ctx.reload_dsl
|
||||
end
|
||||
ctx.load_libraries(libs.map { |x| [x[:content], x[:ref], x[:line]] })
|
||||
|
||||
# hand the context to the profile for further evaluation
|
||||
unless (profile = options['profile']).nil?
|
||||
|
|
|
@ -105,4 +105,12 @@ describe 'inspec exec' do
|
|||
out.stderr.must_equal "This profile requires InSpec version >= 99.0.0. You are running InSpec v#{Inspec::VERSION}.\n"
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with a profile that loads a library and reference' do
|
||||
let(:out) { inspec('exec ' + File.join(profile_path, 'library')) }
|
||||
|
||||
it 'executes the profile without error' do
|
||||
out.exit_status.must_equal 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# encoding: utf-8
|
||||
# copyright: 2015, Chef Software, Inc
|
||||
# license: All rights reserved
|
||||
|
||||
describe gordon do
|
||||
it { should be_enabled }
|
||||
end
|
10
test/unit/mock/profiles/library/inspec.yml
Normal file
10
test/unit/mock/profiles/library/inspec.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: complete
|
||||
title: complete example profile
|
||||
maintainer: Chef Software, Inc.
|
||||
copyright: Chef Software, Inc.
|
||||
copyright_email: support@chef.io
|
||||
license: Proprietary, All rights reserved
|
||||
summary: Testing stub
|
||||
version: 1.0.0
|
||||
supports:
|
||||
- os-family: linux
|
2
test/unit/mock/profiles/library/libraries/gordonlib.rb
Normal file
2
test/unit/mock/profiles/library/libraries/gordonlib.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module GordonLib
|
||||
end
|
12
test/unit/mock/profiles/library/libraries/testlib.rb
Normal file
12
test/unit/mock/profiles/library/libraries/testlib.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Library resource
|
||||
|
||||
require 'gordonlib'
|
||||
require 'hashie'
|
||||
|
||||
class Gordon < Inspec.resource(1)
|
||||
name 'gordon'
|
||||
include GordonLib
|
||||
def enabled?
|
||||
true
|
||||
end
|
||||
end
|
|
@ -307,4 +307,39 @@ describe Inspec::ProfileContext do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'library loading' do
|
||||
it 'supports simple ruby require statements' do
|
||||
# Please note: we do discourage the use of Gems in inspec resources at
|
||||
# this time. Resources should be well packaged whenever possible.
|
||||
proc { profile.load('Net::POP3') }.must_raise NameError
|
||||
profile.load_libraries([['require "net/pop"', 'libraries/a.rb']])
|
||||
profile.load('Net::POP3').to_s.must_equal 'Net::POP3'
|
||||
end
|
||||
|
||||
it 'supports loading across the library' do
|
||||
profile.load_libraries([
|
||||
["require 'a'\nA", 'libraries/b.rb'],
|
||||
['module A; end', 'libraries/a.rb']
|
||||
])
|
||||
profile.load('A').to_s.must_equal 'A'
|
||||
end
|
||||
|
||||
it 'fails loading if reference error occur' do
|
||||
proc {
|
||||
profile.load_libraries([
|
||||
["require 'a'\nB", 'libraries/b.rb'],
|
||||
['module A; end', 'libraries/a.rb']
|
||||
])
|
||||
}.must_raise NameError
|
||||
end
|
||||
|
||||
it 'fails loading if a reference dependency isnt found' do
|
||||
proc {
|
||||
profile.load_libraries([
|
||||
["require 'a'\nA", 'libraries/b.rb'],
|
||||
])
|
||||
}.must_raise LoadError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue