Merge pull request #780 from chef/dr/lib-loading

support intra-libraries file referencing + loading
This commit is contained in:
Dominik Richter 2016-06-03 23:03:07 +02:00
commit e4e29050bb
9 changed files with 152 additions and 6 deletions

View file

@ -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

View 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

View file

@ -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?

View file

@ -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

View file

@ -0,0 +1,7 @@
# encoding: utf-8
# copyright: 2015, Chef Software, Inc
# license: All rights reserved
describe gordon do
it { should be_enabled }
end

View 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

View file

@ -0,0 +1,2 @@
module GordonLib
end

View 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

View file

@ -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