2016-08-31 15:48:51 +00:00
|
|
|
# encoding: utf-8
|
|
|
|
# author: Dominik Richter
|
|
|
|
# author: Christoph Hartmann
|
|
|
|
require 'inspec/dsl'
|
|
|
|
require 'inspec/dsl_shared'
|
|
|
|
|
|
|
|
module Inspec
|
|
|
|
#
|
|
|
|
# ControlEvalContext constructs an anonymous class that control
|
|
|
|
# files will be instance_exec'd against.
|
|
|
|
#
|
|
|
|
# The anonymous class includes the given passed resource_dsl as well
|
|
|
|
# as the basic DSL of the control files (describe, control, title,
|
|
|
|
# etc).
|
|
|
|
#
|
2017-12-07 19:22:55 +00:00
|
|
|
class ControlEvalContext
|
2016-08-31 15:48:51 +00:00
|
|
|
# Create the context for controls. This includes all components of the DSL,
|
|
|
|
# including matchers and resources.
|
|
|
|
#
|
|
|
|
# @param [ResourcesDSL] resources_dsl which has all resources to attach
|
|
|
|
# @return [RuleContext] the inner context of rules
|
2018-09-12 20:42:58 +00:00
|
|
|
def self.rule_context(resources_dsl, profile_id)
|
2016-08-31 15:48:51 +00:00
|
|
|
require 'rspec/core/dsl'
|
|
|
|
Class.new(Inspec::Rule) do
|
|
|
|
include RSpec::Core::DSL
|
2016-09-14 13:57:26 +00:00
|
|
|
with_resource_dsl resources_dsl
|
2018-09-12 20:42:58 +00:00
|
|
|
|
|
|
|
# allow attributes to be accessed within control blocks
|
|
|
|
define_method :attribute do |name|
|
|
|
|
Inspec::AttributeRegistry.find_attribute(name, profile_id).value
|
|
|
|
end
|
2016-08-31 15:48:51 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Creates the heart of the control eval context:
|
|
|
|
#
|
|
|
|
# An instantiated object which has all resources registered to it
|
|
|
|
# and exposes them to the a test file.
|
|
|
|
#
|
|
|
|
# @param profile_context [Inspec::ProfileContext]
|
|
|
|
# @param outer_dsl [OuterDSLClass]
|
|
|
|
# @return [ProfileContextClass]
|
2017-10-17 12:47:30 +00:00
|
|
|
def self.create(profile_context, resources_dsl) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
2016-08-31 15:48:51 +00:00
|
|
|
profile_context_owner = profile_context
|
|
|
|
profile_id = profile_context.profile_id
|
2018-09-12 20:42:58 +00:00
|
|
|
rule_class = rule_context(resources_dsl, profile_id)
|
2016-08-31 15:48:51 +00:00
|
|
|
|
2017-11-21 07:49:41 +00:00
|
|
|
Class.new do # rubocop:disable Metrics/BlockLength
|
2016-08-31 15:48:51 +00:00
|
|
|
include Inspec::DSL
|
|
|
|
include Inspec::DSL::RequireOverride
|
|
|
|
include resources_dsl
|
|
|
|
|
2017-10-17 12:47:30 +00:00
|
|
|
attr_accessor :skip_file
|
|
|
|
|
2017-12-05 13:13:41 +00:00
|
|
|
def initialize(backend, conf, dependencies, require_loader, skip_only_if_eval)
|
2016-08-31 15:48:51 +00:00
|
|
|
@backend = backend
|
|
|
|
@conf = conf
|
|
|
|
@dependencies = dependencies
|
|
|
|
@require_loader = require_loader
|
2018-08-07 17:10:45 +00:00
|
|
|
@skip_file_message = nil
|
2017-10-17 12:47:30 +00:00
|
|
|
@skip_file = false
|
2017-12-05 13:13:41 +00:00
|
|
|
@skip_only_if_eval = skip_only_if_eval
|
2016-08-31 15:48:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
define_method :title do |arg|
|
|
|
|
profile_context_owner.set_header(:title, arg)
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_s
|
2016-09-05 08:28:50 +00:00
|
|
|
"Control Evaluation Context (#{profile_name})"
|
2016-08-31 15:48:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
define_method :profile_name do
|
|
|
|
profile_id
|
|
|
|
end
|
|
|
|
|
|
|
|
define_method :control do |*args, &block|
|
|
|
|
id = args[0]
|
|
|
|
opts = args[1] || {}
|
2017-12-05 13:13:41 +00:00
|
|
|
opts[:skip_only_if_eval] = @skip_only_if_eval
|
2016-08-31 15:48:51 +00:00
|
|
|
register_control(rule_class.new(id, profile_id, opts, &block))
|
|
|
|
end
|
|
|
|
|
2016-09-05 08:28:50 +00:00
|
|
|
#
|
|
|
|
# Describe allows users to write rspec-like bare describe
|
|
|
|
# blocks without declaring an inclosing control. Here, we
|
|
|
|
# generate a control for them automatically and then execute
|
|
|
|
# the describe block in the context of that control.
|
|
|
|
#
|
2016-08-31 15:48:51 +00:00
|
|
|
define_method :describe do |*args, &block|
|
2017-11-21 07:49:41 +00:00
|
|
|
loc = block_location(block, caller(1..1).first)
|
2016-08-31 15:48:51 +00:00
|
|
|
id = "(generated from #{loc} #{SecureRandom.hex})"
|
|
|
|
|
|
|
|
res = nil
|
|
|
|
rule = rule_class.new(id, profile_id, {}) do
|
|
|
|
res = describe(*args, &block)
|
|
|
|
end
|
|
|
|
register_control(rule, &block)
|
|
|
|
|
|
|
|
res
|
|
|
|
end
|
|
|
|
|
2016-09-15 07:54:15 +00:00
|
|
|
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
|
|
|
|
|
2016-08-31 15:48:51 +00:00
|
|
|
define_method :add_resources do |context|
|
|
|
|
self.class.class_eval do
|
|
|
|
include context.to_resources_dsl
|
|
|
|
end
|
|
|
|
|
|
|
|
rule_class.class_eval do
|
|
|
|
include context.to_resources_dsl
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
define_method :add_subcontext do |context|
|
|
|
|
profile_context_owner.add_subcontext(context)
|
|
|
|
end
|
|
|
|
|
|
|
|
define_method :register_control do |control, &block|
|
2018-01-02 19:04:13 +00:00
|
|
|
if @skip_file
|
2018-08-07 17:10:45 +00:00
|
|
|
::Inspec::Rule.set_skip_rule(control, true, @skip_file_message)
|
2016-09-14 07:41:19 +00:00
|
|
|
end
|
2016-08-31 15:48:51 +00:00
|
|
|
|
2018-01-02 19:04:13 +00:00
|
|
|
unless profile_context_owner.profile_supports_platform?
|
|
|
|
platform = inspec.platform
|
|
|
|
msg = "Profile #{profile_context_owner.profile_id} is not supported on platform #{platform.name}/#{platform.release}."
|
2018-08-07 17:10:45 +00:00
|
|
|
::Inspec::Rule.set_skip_rule(control, true, msg)
|
2018-01-02 19:04:13 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
unless profile_context_owner.profile_supports_inspec_version?
|
|
|
|
msg = "Profile #{profile_context_owner.profile_id} is not supported on InSpec version (#{Inspec::VERSION})."
|
2018-08-07 17:10:45 +00:00
|
|
|
::Inspec::Rule.set_skip_rule(control, true, msg)
|
2018-01-02 19:04:13 +00:00
|
|
|
end
|
|
|
|
|
2016-08-31 15:48:51 +00:00
|
|
|
profile_context_owner.register_rule(control, &block) unless control.nil?
|
|
|
|
end
|
|
|
|
|
|
|
|
# method for attributes; import attribute handling
|
2018-09-12 20:42:58 +00:00
|
|
|
define_method :attribute do |name, options = {}|
|
|
|
|
if options.empty?
|
|
|
|
Inspec::AttributeRegistry.find_attribute(name, profile_id).value
|
|
|
|
else
|
|
|
|
profile_context_owner.register_attribute(name, options)
|
|
|
|
end
|
2016-08-31 15:48:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
define_method :skip_control do |id|
|
|
|
|
profile_context_owner.unregister_rule(id)
|
|
|
|
end
|
|
|
|
|
2018-08-07 17:10:45 +00:00
|
|
|
define_method :only_if do |message = nil, &block|
|
2017-10-17 12:47:30 +00:00
|
|
|
return unless block
|
2017-12-05 13:13:41 +00:00
|
|
|
return if @skip_file == true
|
|
|
|
return if @skip_only_if_eval == true
|
|
|
|
|
|
|
|
return if block.yield == true
|
2017-10-17 12:47:30 +00:00
|
|
|
# Apply `set_skip_rule` for other rules in the same file
|
|
|
|
profile_context_owner.rules.values.each do |r|
|
|
|
|
sources_match = r.source_file == block.source_location[0]
|
2018-08-07 17:10:45 +00:00
|
|
|
Inspec::Rule.set_skip_rule(r, true, message) if sources_match
|
2017-10-17 12:47:30 +00:00
|
|
|
end
|
|
|
|
|
2018-08-07 17:10:45 +00:00
|
|
|
@skip_file_message = message
|
2017-10-17 12:47:30 +00:00
|
|
|
@skip_file = true
|
2016-08-31 15:48:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
alias_method :rule, :control
|
|
|
|
alias_method :skip_rule, :skip_control
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def block_location(block, alternate_caller)
|
|
|
|
if block.nil?
|
|
|
|
alternate_caller[/^(.+:\d+):in .+$/, 1] || 'unknown'
|
|
|
|
else
|
|
|
|
path, line = block.source_location
|
|
|
|
"#{File.basename(path)}:#{line}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|