Class: Inspec::Runner

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/inspec/runner.rb

Overview

Inspec::Runner coordinates the running of tests and is the main entry point to the application.

Users are expected to insantiate a runner, add targets to be run, and then call the run method:

“‘ r = Inspec::Runner.new() r.add_target(“/path/to/some/profile”) r.add_target(“url/to/some/profile”) r.run “`

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(conf = {}) ⇒ Runner

Returns a new instance of Runner.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/inspec/runner.rb', line 42

def initialize(conf = {})
  @rules = []
  # If we were handed a Hash config (by audit cookbook or kitchen-inspec),
  # upgrade it to a proper config. This handles a lot of config finalization,
  # like reporter parsing.
  @conf = conf.is_a?(Hash) ? Inspec::Config.new(conf) : conf
  @conf[:logger] ||= Logger.new(nil)
  @target_profiles = []
  @controls = @conf[:controls] || []
  @depends = @conf[:depends] || []
  @create_lockfile = @conf[:create_lockfile]
  @cache = Inspec::Cache.new(@conf[:vendor_cache])

  @test_collector = @conf.delete(:test_collector) || begin
    require 'inspec/runner_rspec'
    RunnerRspec.new(@conf)
  end

  # list of profile inputs
  @inputs = {}

  load_inputs(@conf)
  configure_transport
end

Instance Attribute Details

#backendObject (readonly)

Returns the value of attribute backend.



35
36
37
# File 'lib/inspec/runner.rb', line 35

def backend
  @backend
end

#inputsObject (readonly)

Returns the value of attribute inputs.



35
36
37
# File 'lib/inspec/runner.rb', line 35

def inputs
  @inputs
end

#rulesObject (readonly)

Returns the value of attribute rules.



35
36
37
# File 'lib/inspec/runner.rb', line 35

def rules
  @rules
end

Instance Method Details

#add_target(target, _opts = []) ⇒ Object

add_target allows the user to add a target whose tests will be run when the user calls the run method.

A target is a path or URL that points to a profile. Using this target we generate a Profile and a ProfileContext. The content (libraries, tests, and inputs) from the Profile are loaded into the ProfileContext.

If the profile depends on other profiles, those profiles will be loaded on-demand when include_content or required_content are called using similar code in Inspec::DSL.

Once the we’ve loaded all of the tests files in the profile, we query the profile for the full list of rules. Those rules are registered with the @test_collector which is ultimately responsible for actually running the tests.

TODO: Deduplicate/clarify the loading code that exists in here, the ProfileContext, the Profile, and Inspec::DSL



203
204
205
206
207
208
209
210
211
# File 'lib/inspec/runner.rb', line 203

def add_target(target, _opts = [])
  profile = Inspec::Profile.for_target(target,
                                       vendor_cache: @cache,
                                       backend: @backend,
                                       controls: @controls,
                                       inputs: @conf[:attributes]) # TODO: read form :inputs here (user visible)
  raise "Could not resolve #{target} to valid input." if profile.nil?
  @target_profiles << profile if supports_profile?(profile)
end

#all_rulesObject

In some places we read the rules off of the runner, in other places we read it off of the profile context. To keep the API’s the same, we provide an #all_rules method here as well.



226
227
228
# File 'lib/inspec/runner.rb', line 226

def all_rules
  @rules
end

#attributesObject



37
38
39
40
# File 'lib/inspec/runner.rb', line 37

def attributes
  Inspec.deprecate(:rename_attributes_to_inputs, "Don't call runner.attributes, call runner.inputs")
  inputs
end

#configure_transportObject



71
72
73
74
# File 'lib/inspec/runner.rb', line 71

def configure_transport
  @backend = Inspec::Backend.create(@conf)
  @test_collector.backend = @backend
end

#eval_with_virtual_profile(command) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/inspec/runner.rb', line 240

def eval_with_virtual_profile(command)
  require 'fetchers/mock'
  add_target({ 'inspec.yml' => 'name: inspec-shell' })
  our_profile = @target_profiles.first
  ctx = our_profile.runner_context

  # Load local profile dependencies. This is used in inspec shell
  # to provide access to local profiles that add resources.
  @depends.each do |dep|
    # support for windows paths
    dep = dep.tr('\\', '/')
    Inspec::Profile.for_path(dep, { profile_context: ctx }).load_libraries
  end

  ctx.load(command)
end

#loadObject



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/inspec/runner.rb', line 84

def load
  all_controls = []

  @target_profiles.each do |profile|
    @test_collector.add_profile(profile)
    next unless profile.supports_platform?

    write_lockfile(profile) if @create_lockfile
    profile.locked_dependencies
    profile_context = profile.load_libraries

    profile_context.dependencies.list.values.each do |requirement|
      unless requirement.profile.supports_platform?
        Inspec::Log.warn "Skipping profile: '#{requirement.profile.name}'" \
         " on unsupported platform: '#{@backend.platform.name}/#{@backend.platform.release}'."
        next
      end
      @test_collector.add_profile(requirement.profile)
    end

    @inputs = profile.runner_context.inputs if @inputs.empty?
    tests = profile.collect_tests
    all_controls += tests unless tests.nil?
  end

  all_controls.each do |rule|
    register_rule(rule) unless rule.nil?
  end
end

#load_inputs(options) ⇒ Object

determine all inputs before the execution, fetch data from secrets backend



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/inspec/runner.rb', line 153

def load_inputs(options)
  # TODO: - rename :attributes and :attrs - these are both user-visible
  options[:attributes] ||= {}

  secrets_targets = options[:attrs]
  return options[:attributes] if secrets_targets.nil?

  secrets_targets.each do |target|
    validate_inputs_file_readability!(target)

    secrets = Inspec::SecretsBackend.resolve(target)
    if secrets.nil?
      raise Inspec::Exceptions::SecretsBackendNotFound,
            "Cannot find parser for inputs file '#{target}'. " \
            'Check to make sure file has the appropriate extension.'
    end

    next if secrets.inputs.nil?
    options[:attributes].merge!(secrets.inputs)
  end

  options[:attributes]
end

#register_rules(ctx) ⇒ Object



230
231
232
233
234
235
236
237
238
# File 'lib/inspec/runner.rb', line 230

def register_rules(ctx)
  new_tests = false
  ctx.rules.each do |rule_id, rule|
    next if block_given? && !(yield rule_id, rule)
    new_tests = true
    register_rule(rule)
  end
  new_tests
end

#render_output(run_data) ⇒ Object



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

def render_output(run_data)
  return if @conf['reporter'].nil?

  @conf['reporter'].each do |reporter|
    result = Inspec::Reporters.render(reporter, run_data)
    raise Inspec::ReporterError, "Error generating reporter '#{reporter[0]}'" if result == false
  end
end

#reportObject



129
130
131
# File 'lib/inspec/runner.rb', line 129

def report
  Inspec::Reporters.report(@conf['reporter'].first, @run_data)
end

#resetObject



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

def reset
  @test_collector.reset
  @target_profiles.each do |profile|
    profile.runner_context.rules = {}
  end
  @rules = []
end

#run(with = nil) ⇒ Object



114
115
116
117
118
# File 'lib/inspec/runner.rb', line 114

def run(with = nil)
  Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}"
  load
  run_tests(with)
end

#run_tests(with = nil) ⇒ Object



145
146
147
148
149
150
# File 'lib/inspec/runner.rb', line 145

def run_tests(with = nil)
  @run_data = @test_collector.run(with)
  # dont output anything if we want a report
  render_output(@run_data) unless @conf['report']
  @test_collector.exit_code
end

#supports_profile?(profile) ⇒ Boolean

Returns:

  • (Boolean)


213
214
215
216
217
218
219
220
221
# File 'lib/inspec/runner.rb', line 213

def supports_profile?(profile)
  if !profile.supports_runtime?
    raise 'This profile requires InSpec version '\
         "#{profile..inspec_requirement}. You are running "\
         "InSpec v#{Inspec::VERSION}.\n"
  end

  true
end

#testsObject



67
68
69
# File 'lib/inspec/runner.rb', line 67

def tests
  @test_collector.tests
end

#write_lockfile(profile) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
# File 'lib/inspec/runner.rb', line 133

def write_lockfile(profile)
  return false if !profile.writable?

  if profile.lockfile_exists?
    Inspec::Log.debug "Using existing lockfile #{profile.lockfile_path}"
  else
    Inspec::Log.debug "Creating lockfile: #{profile.lockfile_path}"
    lockfile = profile.generate_lockfile
    File.write(profile.lockfile_path, lockfile.to_yaml)
  end
end