mirror of
https://github.com/inspec/inspec
synced 2024-11-27 07:00:39 +00:00
Allow users to reference resources from dependencies
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. The new `require_resource` dsl method allows the user to do the following: require_resource(profile: 'profile_name', resource: 'other', as: 'renamed') describe renamed do ... end Signed-off-by: Steven Danna <steve@chef.io>
This commit is contained in:
parent
31967fa4b2
commit
b2146d8758
10 changed files with 138 additions and 12 deletions
|
@ -92,6 +92,14 @@ module Inspec
|
|||
res
|
||||
end
|
||||
|
||||
define_method :add_resource do |name, new_res|
|
||||
resources_dsl.module_exec do
|
||||
define_method name.to_sym do |*args|
|
||||
new_res.new(@backend, name.to_s, *args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
define_method :add_resources do |context|
|
||||
self.class.class_eval do
|
||||
include context.to_resources_dsl
|
||||
|
|
|
@ -18,6 +18,15 @@ module Inspec::DSL
|
|||
alias require_rules require_controls
|
||||
alias include_rules include_controls
|
||||
|
||||
def require_resource(options = {})
|
||||
fail 'You must specify a specific resource name when calling require_resource()' if options[:resource].nil?
|
||||
|
||||
from_profile = options[:profile] || profile_name
|
||||
target_name = options[:as] || options[:resource]
|
||||
res = resource_class(from_profile, options[:resource])
|
||||
add_resource(target_name, res)
|
||||
end
|
||||
|
||||
def self.load_spec_files_for_profile(bind_context, opts, &block)
|
||||
dependencies = opts[:dependencies]
|
||||
profile_id = opts[:profile_id]
|
||||
|
|
|
@ -72,6 +72,9 @@ module 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
|
||||
|
|
|
@ -66,6 +66,7 @@ module Inspec
|
|||
@backend = options[:backend] || Inspec::Backend.create(options)
|
||||
@source_reader = source_reader
|
||||
@tests_collected = false
|
||||
@libraries_loaded = false
|
||||
Metadata.finalize(@source_reader.metadata, @profile_id)
|
||||
@runner_context = options[:profile_context] || Inspec::ProfileContext.for_profile(self,
|
||||
@backend,
|
||||
|
@ -125,6 +126,8 @@ module Inspec
|
|||
end
|
||||
|
||||
def load_libraries
|
||||
return @runner_context if @libraries_loaded
|
||||
|
||||
locked_dependencies.each do |d|
|
||||
c = d.load_libraries
|
||||
@runner_context.add_resources(c)
|
||||
|
@ -135,6 +138,7 @@ module Inspec
|
|||
end
|
||||
|
||||
@runner_context.load_libraries(libs)
|
||||
@libraries_loaded = true
|
||||
@runner_context
|
||||
end
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ module Inspec
|
|||
'attributes' => attributes })
|
||||
end
|
||||
|
||||
attr_reader :attributes, :profile_id, :resource_registry
|
||||
attr_reader :attributes, :profile_id, :resource_registry, :backend
|
||||
attr_accessor :rules
|
||||
def initialize(profile_id, backend, conf)
|
||||
if backend.nil?
|
||||
|
@ -28,7 +28,8 @@ module Inspec
|
|||
@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
|
||||
|
@ -46,7 +47,7 @@ module Inspec
|
|||
end
|
||||
|
||||
def to_resources_dsl
|
||||
Inspec::Resource.create_dsl(@backend, @resource_registry)
|
||||
Inspec::Resource.create_dsl(self)
|
||||
end
|
||||
|
||||
def control_eval_context
|
||||
|
@ -66,20 +67,34 @@ module Inspec
|
|||
@conf['profile'].supports_os?
|
||||
end
|
||||
|
||||
def all_rules
|
||||
def all_controls
|
||||
ret = @rules.values
|
||||
ret += @subcontexts.map(&:all_rules).flatten
|
||||
ret += @control_subcontexts.map(&:all_rules).flatten
|
||||
ret
|
||||
end
|
||||
alias all_rules all_controls
|
||||
|
||||
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)
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
require 'inspec/plugins'
|
||||
|
||||
module Inspec
|
||||
class ProfileNotFound < StandardError; end
|
||||
|
||||
class Resource
|
||||
def self.default_registry
|
||||
@default_registry ||= {}
|
||||
|
@ -25,9 +27,22 @@ module Inspec
|
|||
#
|
||||
# @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_context)
|
||||
backend = profile_context.backend
|
||||
my_registry = profile_context.resource_registry
|
||||
|
||||
Module.new do
|
||||
define_method :resource_class do |profile_name, resource_name|
|
||||
inner_context = if profile_name == profile_context.profile_id
|
||||
profile_context
|
||||
else
|
||||
profile_context.subcontext_by_name(profile_name)
|
||||
end
|
||||
|
||||
fail ProfileNotFound, "Cannot find profile named: #{profile_name}" if inner_context.nil?
|
||||
inner_context.resource_registry[resource_name]
|
||||
end
|
||||
|
||||
my_registry.each do |id, r|
|
||||
define_method id.to_sym do |*args|
|
||||
r.new(backend, id.to_s, *args)
|
||||
|
|
|
@ -176,6 +176,15 @@ Test Summary: \e[32m2 successful\e[0m, \e[31m0 failures\e[0m, \e[37m0 skipped\e[
|
|||
end
|
||||
end
|
||||
|
||||
describe 'using namespaced resources' do
|
||||
it 'works' do
|
||||
out = inspec('exec ' + File.join(profile_path, 'dependencies', 'resource-namespace'))
|
||||
out.stderr.must_equal ''
|
||||
out.exit_status.must_equal 0
|
||||
out.stdout.force_encoding(Encoding::UTF_8).must_include "Summary: \e[32m5 successful\e[0m, \e[31m0 failures\e[0m, \e[37m0 skipped\e[0m\n"
|
||||
end
|
||||
end
|
||||
|
||||
describe "with a 2-level dependency tree" do
|
||||
it 'correctly runs tests from the whole tree' do
|
||||
out = inspec('exec ' + File.join(profile_path, 'dependencies', 'inheritance'))
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# encoding: utf-8
|
||||
# copyright: 2015, The Authors
|
||||
# license: All rights reserved
|
||||
require_resource(profile: 'profile_c', resource: 'gordon_config', as: 'gordy_config')
|
||||
|
||||
describe gordy_config do
|
||||
its('version') { should eq('1.0') }
|
||||
end
|
||||
|
||||
control 'whichgordon' do
|
||||
describe gordy_config do
|
||||
its('version') { should eq('1.0') }
|
||||
end
|
||||
|
||||
describe gordon_config do
|
||||
its('version') { should eq('2.0') }
|
||||
end
|
||||
|
||||
describe gordy_config do
|
||||
its('version') { should eq(gordy_config.version) }
|
||||
end
|
||||
|
||||
describe gordon_config do
|
||||
its('version') { should eq(gordon_config.version) }
|
||||
end
|
||||
|
||||
end
|
|
@ -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'
|
|
@ -2,7 +2,6 @@
|
|||
# author: Steven Danna
|
||||
|
||||
require 'helper'
|
||||
require 'rspec/core'
|
||||
require 'inspec/control_eval_context'
|
||||
|
||||
describe Inspec::ControlEvalContext do
|
||||
|
@ -11,7 +10,6 @@ describe Inspec::ControlEvalContext do
|
|||
"wombat"
|
||||
end
|
||||
end
|
||||
Inspec::Log.level = :debug
|
||||
|
||||
let(:control_content) { <<EOF
|
||||
control 'foo' do
|
||||
|
@ -44,11 +42,36 @@ EOF
|
|||
profile_context.stubs(:current_load).returns({file: "<test content>"})
|
||||
eval_context.instance_eval(control_content)
|
||||
profile_context.all_rules.each do |rule|
|
||||
# Turn each rule into an example group and run it, none of the example content should raise an
|
||||
# exception
|
||||
# Turn each rule into an example group and run it, none of the
|
||||
# example content should raise an exception
|
||||
Inspec::Rule.prepare_checks(rule).each do |m, a, b|
|
||||
# if we require this at the top level, none of the other tests
|
||||
# in this file will run. itsfine.jpg
|
||||
require 'rspec/core'
|
||||
RSpec::Core::ExampleGroup.describe(*a, &b).run
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#resource" do
|
||||
let(:resource_dsl) { Inspec::Resource.create_dsl(profile_context) }
|
||||
let(:inner_context) { Inspec::ProfileContext.new('inner-context', backend, {}) }
|
||||
let(:control_content) do <<EOF
|
||||
resource('profile_a', 'foobar')
|
||||
EOF
|
||||
end
|
||||
|
||||
it "fails if the requested profile can't be found" do
|
||||
assert_raises(Inspec::ProfileNotFound) {
|
||||
eval_context.instance_eval(control_content).must_raise
|
||||
}
|
||||
end
|
||||
|
||||
it "returns the resource from a subcontext" do
|
||||
profile_context.expects(:subcontext_by_name).with('profile_a').returns(inner_context)
|
||||
inner_context.expects(:resource_registry).returns({'foobar' => mock(:new => 'newfoo')})
|
||||
eval_context.instance_eval(control_content).must_equal "newfoo"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue