Skip to content

Commit

Permalink
Allow users to reference resources from dependencies
Browse files Browse the repository at this point in the history
All resources from deps are added into the control_eval_context used by
the current profile.  However, if there is a name conflict, the last
loaded resource wins.  This new syntax:

   resource(profile_name, resource_name, resource_args)

allows users to specify the exact resource they mean.

Signed-off-by: Steven Danna <steve@chef.io>
  • Loading branch information
stevendanna committed Sep 15, 2016
1 parent 913a8fd commit 3130afc
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 58 deletions.
3 changes: 3 additions & 0 deletions lib/inspec/plugins/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ def inspec
end

# rubocop:enable Lint/NestedMethodDefinition
if __resource_registry.key?(name)
Inspec::Log.warn("Overwriting resource #{name}. To reference a specific version of #{name} use the resource() method")
end
__resource_registry[name] = cl
end
end
Expand Down
24 changes: 19 additions & 5 deletions lib/inspec/profile_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def self.for_profile(profile, backend, attributes)
'attributes' => attributes })
end

attr_reader :attributes, :rules, :profile_id, :resource_registry
attr_reader :attributes, :rules, :profile_id, :resource_registry, :backend
def initialize(profile_id, backend, conf)
if backend.nil?
fail 'ProfileContext is initiated with a backend == nil. ' \
Expand All @@ -27,7 +27,8 @@ def initialize(profile_id, backend, conf)
@backend = backend
@conf = conf.dup
@rules = {}
@subcontexts = []
@control_subcontexts = []
@lib_subcontexts = []
@require_loader = ::Inspec::RequireLoader.new
@attributes = []
# A local resource registry that only contains resources defined
Expand All @@ -45,7 +46,7 @@ def dependencies
end

def to_resources_dsl
Inspec::Resource.create_dsl(@backend, @resource_registry)
Inspec::Resource.create_dsl(self)
end

def control_eval_context
Expand All @@ -67,18 +68,31 @@ def profile_supports_os?

def all_rules
ret = @rules.values
ret += @subcontexts.map(&:all_rules).flatten
ret += @control_subcontexts.map(&:all_rules).flatten
ret
end

def subcontext_by_name(name)
found = @lib_subcontexts.find { |c| c.profile_id == name }
if !found
@lib_subcontexts.each do |c|
found = c.subcontext_by_name(name)
break if found
end
end

found
end

def add_resources(context)
@resource_registry.merge!(context.resource_registry)
control_eval_context.add_resources(context)
@lib_subcontexts << context
reload_dsl
end

def add_subcontext(context)
@subcontexts << context
@control_subcontexts << context
end

def load_libraries(libs)
Expand Down
13 changes: 11 additions & 2 deletions lib/inspec/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,18 @@ def self.new_registry
#
# @param backend [BackendRunner] exposing the target to resources
# @return [ResourcesDSL]
def self.create_dsl(backend, my_registry = registry)
# need the local name, to use it in the module creation further down
def self.create_dsl(profile)
backend = profile.backend
my_registry = profile.resource_registry

Module.new do
define_method :resource do |profile_name, resource_name, *args|
inner_context = profile.subcontext_by_name(profile_name)
fail "Cannot find profile named: #{profile_name} in #{profile_context_owner.subcontexts_for_libs}" if inner_context.nil?

inner_context.resource_registry[resource_name].new(backend, resource_name, *args)
end

my_registry.each do |id, r|
define_method id.to_sym do |*args|
r.new(backend, id.to_s, *args)
Expand Down
52 changes: 2 additions & 50 deletions lib/inspec/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module Inspec
# r.run
# ```
#
class Runner # rubocop:disable Metrics/ClassLength
class Runner
extend Forwardable

def_delegator :@test_collector, :report
Expand Down Expand Up @@ -182,58 +182,10 @@ def register_rules(ctx)

private

def block_source_info(block)
return {} if block.nil? || !block.respond_to?(:source_location)
opts = {}
file_path, line = block.source_location
opts['file_path'] = file_path
opts['line_number'] = line
opts
end

def get_check_example(method_name, arg, block)
opts = block_source_info(block)

if !arg.empty? &&
arg[0].respond_to?(:resource_skipped) &&
!arg[0].resource_skipped.nil?
return @test_collector.example_group(*arg, opts) do
it arg[0].resource_skipped
end
else
# add the resource
case method_name
when 'describe'
return @test_collector.example_group(*arg, opts, &block)
when 'expect'
return block.example_group
when 'describe.one'
tests = arg.map do |x|
@test_collector.example_group(x[1][0], block_source_info(x[2]), &x[2])
end
return nil if tests.empty?
ok_tests = tests.find_all(&:run)
# return all tests if none succeeds; we will just report full failure
return tests if ok_tests.empty?
# otherwise return all working tests
return ok_tests
else
fail "A rule was registered with #{method_name.inspect}, "\
"which isn't understood and cannot be processed."
end
end
nil
end

def register_rule(rule)
Inspec::Log.debug "Registering rule #{rule}"
@rules << rule
checks = ::Inspec::Rule.prepare_checks(rule)
examples = checks.flat_map do |m, a, b|
get_check_example(m, a, b)
end.compact

examples.each { |e| @test_collector.add_test(e, rule) }
@test_collector.add_rule(rule)
end
end
end
62 changes: 61 additions & 1 deletion lib/inspec/runner_rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# collide and disable all unit tests that have been added.

module Inspec
class RunnerRspec
class RunnerRspec # rubocop:disable Metrics/ClassLength
def initialize(conf)
@conf = conf
@formatter = nil
Expand Down Expand Up @@ -96,6 +96,21 @@ def reset
configure_output
end

#
# Add an Inspec::Rule to the set of tests we are going to run.
# First rules must be converted to Rspec::ExampleGroup's
#
# @params rule [Inspec::Rule]
#
def add_rule(rule)
checks = ::Inspec::Rule.prepare_checks(rule)
examples = checks.flat_map do |m, a, b|
check_to_example(m, a, b)
end.compact

examples.each { |e| add_test(e, rule) }
end

private

FORMATTERS = {
Expand All @@ -105,6 +120,51 @@ def reset
'cli' => 'InspecRspecCli',
}.freeze

def block_source_info(block)
return {} if block.nil? || !block.respond_to?(:source_location)
opts = {}
file_path, line = block.source_location
opts['file_path'] = file_path
opts['line_number'] = line
opts
end

def check_to_example(method_name, arg, block)
opts = block_source_info(block)

skip_check = !arg.empty? && arg[0].respond_to?(:resource_skipped) && !arg[0].resource_skipped.nil?

if skip_check
example_group(*arg, opts) do
it arg[0].resource_skipped
end
else
case method_name
when 'describe'
example_group(*arg, opts, &block)
when 'expect'
block.example_group
when 'describe.one'
tests = arg.map do |x|
example_group(x[1][0], block_source_info(x[2]), &x[2])
end

return nil if tests.empty?
ok_tests = tests.find_all(&:run)

# return all tests if none succeeds; we will just report full failure
if ok_tests.empty?
tests
else
ok_tests
end
else
fail "A rule was registered with #{method_name.inspect}, "\
"which isn't understood and cannot be processed."
end
end
end

# Configure the output formatter and stream to be used with RSpec.
#
# @return [nil]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# encoding: utf-8
# copyright: 2015, The Authors
# license: All rights reserved
control 'whichgordon' do
describe gordon_config do
its('version') { should eq('2.0') }
end

describe resource('profile_d', 'gordon_config') do
its('version') { should eq('2.0') }
end

describe resource('profile_c', 'gordon_config') do
its('version') { should eq('1.0') }
end
end
13 changes: 13 additions & 0 deletions test/unit/mock/profiles/dependencies/resource-namespace/inspec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: resource-namespace
title: InSpec Profile
maintainer: The Authors
copyright: The Authors
copyright_email: you@example.com
license: All Rights Reserved
summary: An InSpec Compliance Profile
version: 0.1.0
depends:
- name: profile_a
path: '../profile_a'
- name: profile_d
path: '../profile_d'
Empty file.

0 comments on commit 3130afc

Please sign in to comment.