Class: Inspec::Rule

Inherits:
Object
  • Object
show all
Includes:
RSpec::Matchers
Defined in:
lib/inspec/rule.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(id, profile_id, resource_dsl, opts, &block) ⇒ Rule

Returns a new instance of Rule.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/inspec/rule.rb', line 20

def initialize(id, profile_id, resource_dsl, opts, &block)
  @impact = nil
  @title = nil
  @descriptions = {}
  @refs = []
  @tags = {}

  @resource_dsl = resource_dsl
  extend resource_dsl # TODO: remove! do it via method_missing

  # not changeable by the user:
  @__code = nil
  @__block = block
  @__source_location = __get_block_source_location(&block)
  @__rule_id = id
  @__profile_id = profile_id
  @__checks = []
  @__skip_rule = {} # { result: true, message: "Why", type: [:only_if, :waiver] }
  @__merge_count = 0
  @__merge_changes = []
  @__skip_only_if_eval = opts[:skip_only_if_eval]

  # evaluate the given definition
  return unless block_given?

  begin
    instance_eval(&block)

    # By applying waivers *after* the instance eval, we assure that
    # waivers have higher precedence than only_if.
    __apply_waivers

  rescue SystemStackError, StandardError => e
    # We've encountered an exception while trying to eval the code inside the
    # control block. We need to prevent the exception from bubbling up, and
    # fail the control. Controls are failed by having a failed resource within
    # them; but since our control block is unsafe (and opaque) to us, let's
    # make a dummy and fail that.
    location = block.source_location.compact.join(":")
    describe "Control Source Code Error" do
      # Rubocop thinks we are raising an exception - we're actually calling RSpec's fail()
      its(location) { fail e.message } # rubocop: disable Style/SignalException
    end
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *arguments, &block) ⇒ Object

Support for Control DSL plugins. This is called when an unknown method is encountered within a control block.



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/inspec/rule.rb', line 209

def method_missing(method_name, *arguments, &block)
  # Check to see if there is a control_dsl plugin activator hook with the method name
  registry = Inspec::Plugin::V2::Registry.instance
  hook = registry.find_activators(plugin_type: :control_dsl, activator_name: method_name).first
  if hook
    # OK, load the hook if it hasn't been already.  We'll then know a module,
    # which we can then inject into the context
    hook.activate

    # Inject the module's methods into the context.
    # implementation_class is the field name, but this is actually a module.
    self.class.include(hook.implementation_class)
    # Now that the module is loaded, it defined one or more methods
    # (presumably the one we were looking for.)
    # We still haven't called it, so do so now.
    send(method_name, *arguments, &block)
  else
    begin
      Inspec::DSL.method_missing_resource(inspec, method_name, *arguments)
    rescue LoadError
      super
    end
  end
end

Instance Attribute Details

#__profile_idObject (readonly)

Returns the value of attribute __profile_id.



18
19
20
# File 'lib/inspec/rule.rb', line 18

def __profile_id
  @__profile_id
end

#__waiver_dataObject (readonly)

Returns the value of attribute __waiver_data.



16
17
18
# File 'lib/inspec/rule.rb', line 16

def __waiver_data
  @__waiver_data
end

#resource_dslObject

Returns the value of attribute resource_dsl.



17
18
19
# File 'lib/inspec/rule.rb', line 17

def resource_dsl
  @resource_dsl
end

Class Method Details

.checks(rule) ⇒ Object



247
248
249
# File 'lib/inspec/rule.rb', line 247

def self.checks(rule)
  rule.instance_variable_get(:@__checks)
end

.merge(dst, src) ⇒ Object

rubocop:disable Metrics/AbcSize



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/inspec/rule.rb', line 289

def self.merge(dst, src) # rubocop:disable Metrics/AbcSize
  if src.id != dst.id
    # TODO: register an error, this case should not happen
    return
  end

  sp = rule_id(src)
  dp = rule_id(dst)
  if sp != dp
    # TODO: register an error, this case should not happen
    return
  end

  # merge all fields
  dst.impact(src.impact)                 unless src.impact.nil?
  dst.title(src.title)                   unless src.title.nil?
  dst.descriptions(src.descriptions)     unless src.descriptions.nil?
  dst.tag(src.tag)                       unless src.tag.nil?
  dst.ref(src.ref)                       unless src.ref.nil?

  # merge indirect fields
  # checks defined in the source will completely eliminate
  # all checks that were defined in the destination
  sc = checks(src)
  dst.instance_variable_set(:@__checks, sc) unless sc.empty?
  skip_check = skip_status(src)
  sr = skip_check[:result]
  msg = skip_check[:message]
  skip_type = skip_check[:type]
  set_skip_rule(dst, sr, msg, skip_type) unless sr.nil?

  # Save merge history
  dst.instance_variable_set(:@__merge_count, merge_count(dst) + 1)
  dst.instance_variable_set(
    :@__merge_changes,
    merge_changes(dst) << src.instance_variable_get(:@__source_location)
  )
end

.merge_changes(rule) ⇒ Object



268
269
270
# File 'lib/inspec/rule.rb', line 268

def self.merge_changes(rule)
  rule.instance_variable_get(:@__merge_changes)
end

.merge_count(rule) ⇒ Object



264
265
266
# File 'lib/inspec/rule.rb', line 264

def self.merge_count(rule)
  rule.instance_variable_get(:@__merge_count)
end

.prepare_checks(rule) ⇒ Object

If a rule is marked to be skipped, this creates a dummay array of “checks” with a skip outcome



274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/inspec/rule.rb', line 274

def self.prepare_checks(rule)
  skip_check = skip_status(rule)
  return checks(rule) unless skip_check[:result].eql?(true)

  if skip_check[:message]
    msg = "Skipped control due to #{skip_check[:type]} condition: #{skip_check[:message]}"
  else
    msg = "Skipped control due to #{skip_check[:type]} condition."
  end

  resource = rule.noop
  resource.skip_resource(msg)
  [["describe", [resource], nil]]
end

.profile_id(rule) ⇒ Object



243
244
245
# File 'lib/inspec/rule.rb', line 243

def self.profile_id(rule)
  rule.instance_variable_get(:@__profile_id)
end

.rule_id(rule) ⇒ Object

TODO: figure out why these violations exist and nuke them.



235
236
237
# File 'lib/inspec/rule.rb', line 235

def self.rule_id(rule)
  rule.instance_variable_get(:@__rule_id)
end

.set_rule_id(rule, value) ⇒ Object



239
240
241
# File 'lib/inspec/rule.rb', line 239

def self.set_rule_id(rule, value)
  rule.instance_variable_set(:@__rule_id, value)
end

.set_skip_rule(rule, value, message = nil, type = :only_if) ⇒ Object



255
256
257
258
259
260
261
262
# File 'lib/inspec/rule.rb', line 255

def self.set_skip_rule(rule, value, message = nil, type = :only_if)
  rule.instance_variable_set(:@__skip_rule,
                             {
                               result: value,
                               message: message,
                               type: type,
                             })
end

.skip_status(rule) ⇒ Object



251
252
253
# File 'lib/inspec/rule.rb', line 251

def self.skip_status(rule)
  rule.instance_variable_get(:@__skip_rule)
end

Instance Method Details

#attribute(name, options = {}) ⇒ Object



201
202
203
204
# File 'lib/inspec/rule.rb', line 201

def attribute(name, options = {})
  Inspec.deprecate(:attrs_dsl, "Input name: #{name}, Profile: #{__profile_id}")
  input(name, options)
end

#desc(v = nil, data = nil) ⇒ Object



90
91
92
93
94
95
96
97
98
# File 'lib/inspec/rule.rb', line 90

def desc(v = nil, data = nil)
  return @descriptions[:default] if v.nil?

  if data.nil?
    @descriptions[:default] = unindent(v)
  else
    @descriptions[v.to_sym] = unindent(data)
  end
end

#describe(*values, &block) ⇒ nil|DescribeBase

Describe will add one or more tests to this control. There is 2 ways of calling it:

describe resource do ... end

or

describe.one do ... end

Parameters:

  • Resource (any)

    to be describe, string, or nil

  • An (Proc)

    optional block containing tests for the described resource

Returns:

  • (nil|DescribeBase)

    if called without arguments, returns DescribeBase



157
158
159
160
161
162
163
164
165
166
# File 'lib/inspec/rule.rb', line 157

def describe(*values, &block)
  if values.empty? && !block_given?
    dsl = resource_dsl
    Class.new(DescribeBase) do
      include dsl
    end.new(method(:__add_check))
  else
    __add_check("describe", values, with_dsl(block))
  end
end

#descriptions(description_hash = nil) ⇒ Object



100
101
102
103
104
# File 'lib/inspec/rule.rb', line 100

def descriptions(description_hash = nil)
  return @descriptions if description_hash.nil?

  @descriptions.merge!(description_hash)
end

#expect(value, &block) ⇒ Object



168
169
170
171
172
# File 'lib/inspec/rule.rb', line 168

def expect(value, &block)
  target = Inspec::Expect.new(value, &with_dsl(block))
  __add_check("expect", [value], target)
  target
end

#id(*_) ⇒ Object



70
71
72
73
# File 'lib/inspec/rule.rb', line 70

def id(*_)
  # never overwrite the ID
  @id
end

#impact(v = nil) ⇒ Object



75
76
77
78
79
80
81
82
83
# File 'lib/inspec/rule.rb', line 75

def impact(v = nil)
  if v.is_a?(String)
    @impact = Inspec::Impact.impact_from_string(v)
  elsif !v.nil?
    @impact = v
  end

  @impact
end

#input(input_name, options = {}) ⇒ Object

allow attributes to be accessed within control blocks



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/inspec/rule.rb', line 175

def input(input_name, options = {})
  if options.empty?
    # Simply an access, no event here
    Inspec::InputRegistry.find_or_register_input(input_name, __profile_id).value
  else
    options[:priority] ||= 20
    options[:provider] = :inline_control_code
    evt = Inspec::Input.infer_event(options)
    Inspec::InputRegistry.find_or_register_input(
      input_name,
      __profile_id,
      type: options[:type],
      required: options[:required],
      description: options[:description],
      pattern: options[:pattern],
      event: evt
    ).value
  end
end

#input_object(input_name) ⇒ Object

Find the Input object, but don’t collapse to a value. Will return nil on a miss.



197
198
199
# File 'lib/inspec/rule.rb', line 197

def input_object(input_name)
  Inspec::InputRegistry.find_or_register_input(input_name, __profile_id)
end

#only_if(message = nil) ⇒ nil

Skip all checks if only_if is false

Parameters:

  • &block (Type)

    returns true if tests are added, false otherwise

Returns:

  • (nil)


136
137
138
139
140
141
142
143
# File 'lib/inspec/rule.rb', line 136

def only_if(message = nil)
  return unless block_given?
  return if @__skip_only_if_eval == true

  @__skip_rule[:result] ||= !yield
  @__skip_rule[:type] = :only_if
  @__skip_rule[:message] = message
end

#ref(ref = nil, opts = {}) ⇒ Object



106
107
108
109
110
111
112
113
114
115
# File 'lib/inspec/rule.rb', line 106

def ref(ref = nil, opts = {})
  return @refs if ref.nil? && opts.empty?

  if opts.empty? && ref.is_a?(Hash)
    opts = ref
  else
    opts[:ref] = ref
  end
  @refs.push(opts)
end

#source_fileObject



128
129
130
# File 'lib/inspec/rule.rb', line 128

def source_file
  @__file
end

#tag(*args) ⇒ Object



117
118
119
120
121
122
123
124
125
126
# File 'lib/inspec/rule.rb', line 117

def tag(*args)
  args.each do |arg|
    if arg.is_a?(Hash)
      @tags.merge!(arg)
    else
      @tags[arg] ||= nil
    end
  end
  @tags
end

#title(v = nil) ⇒ Object



85
86
87
88
# File 'lib/inspec/rule.rb', line 85

def title(v = nil)
  @title = v unless v.nil?
  @title
end

#to_sObject



66
67
68
# File 'lib/inspec/rule.rb', line 66

def to_s
  Inspec::Rule.rule_id(self)
end