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, opts, &block) ⇒ Rule

Returns a new instance of Rule.



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
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/inspec/rule.rb', line 35

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

  # 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

Instance Attribute Details

#__waiver_dataObject (readonly)

Returns the value of attribute __waiver_data.



34
35
36
# File 'lib/inspec/rule.rb', line 34

def __waiver_data
  @__waiver_data
end

Class Method Details

.checks(rule) ⇒ Object



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

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

.merge(dst, src) ⇒ Object

rubocop:disable Metrics/AbcSize



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/inspec/rule.rb', line 240

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



219
220
221
# File 'lib/inspec/rule.rb', line 219

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

.merge_count(rule) ⇒ Object



215
216
217
# File 'lib/inspec/rule.rb', line 215

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



225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/inspec/rule.rb', line 225

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



194
195
196
# File 'lib/inspec/rule.rb', line 194

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

.resource_dslObject

rubocop:disable Style/TrivialAccessors



30
31
32
# File 'lib/inspec/rule.rb', line 30

def self.resource_dsl # rubocop:disable Style/TrivialAccessors
  @resource_dsl
end

.rule_id(rule) ⇒ Object



186
187
188
# File 'lib/inspec/rule.rb', line 186

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

.set_rule_id(rule, value) ⇒ Object



190
191
192
# File 'lib/inspec/rule.rb', line 190

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



206
207
208
209
210
211
212
213
# File 'lib/inspec/rule.rb', line 206

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



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

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

.with_resource_dsl(resource_dsl) ⇒ Object

Include any resources from the given resource DSL. The passed resource_dsl will also be included in any Inspec::Expect objects we make.



24
25
26
27
28
# File 'lib/inspec/rule.rb', line 24

def self.with_resource_dsl(resource_dsl)
  include resource_dsl
  @resource_dsl = resource_dsl
  true
end

Instance Method Details

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



102
103
104
105
106
107
108
109
110
# File 'lib/inspec/rule.rb', line 102

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



169
170
171
172
173
174
175
176
177
178
# File 'lib/inspec/rule.rb', line 169

def describe(*values, &block)
  if values.empty? && !block_given?
    dsl = self.class.ancestors[1]
    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



112
113
114
115
116
# File 'lib/inspec/rule.rb', line 112

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

  @descriptions.merge!(description_hash)
end

#expect(value, &block) ⇒ Object



180
181
182
183
184
# File 'lib/inspec/rule.rb', line 180

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

#id(*_) ⇒ Object



82
83
84
85
# File 'lib/inspec/rule.rb', line 82

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

#impact(v = nil) ⇒ Object



87
88
89
90
91
92
93
94
95
# File 'lib/inspec/rule.rb', line 87

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

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


148
149
150
151
152
153
154
155
# File 'lib/inspec/rule.rb', line 148

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



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

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



140
141
142
# File 'lib/inspec/rule.rb', line 140

def source_file
  @__file
end

#tag(*args) ⇒ Object



129
130
131
132
133
134
135
136
137
138
# File 'lib/inspec/rule.rb', line 129

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



97
98
99
100
# File 'lib/inspec/rule.rb', line 97

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

#to_sObject



78
79
80
# File 'lib/inspec/rule.rb', line 78

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