mirror of
https://github.com/inspec/inspec
synced 2024-11-10 07:04:15 +00:00
Merge pull request #5829 from inspec/nm/streaming-reporters
CFINSPEC-9 Added support for streaming reporters
This commit is contained in:
commit
a9960f9b81
10 changed files with 230 additions and 1 deletions
|
@ -488,6 +488,72 @@ v0.1.0 - Initial version
|
|||
v0.2.0 - added `run_data.profiles[0].inputs[0].options.sensitive`
|
||||
v0.3.0 - added resource_name && params
|
||||
|
||||
## Implementing Streaming Reporter Plugins
|
||||
|
||||
Streaming Reporter plugins offer the opportunity to customize or create a plugin which operates real-time as the Chef Inspec tests runs. Streaming reporters perform streaming using RSpec custom formatters.
|
||||
|
||||
### Declare your plugin activators
|
||||
|
||||
In your `plugin.rb`, include one or more `streaming_reporter` activation blocks. The activation block name will be matched against the value passed into the `--reporter` option. If a match occurs, your activator will fire, which loads any needed libraries, and return your implementation class.
|
||||
|
||||
#### Streaming Reporter Activator Example
|
||||
|
||||
```ruby
|
||||
|
||||
# In plugin.rb
|
||||
module InspecPlugins::Sweeten
|
||||
class Plugin < Inspec.plugin(2)
|
||||
# ... other plugin stuff
|
||||
|
||||
streaming_reporter :streaming_sweet do
|
||||
require_relative 'streaming_reporter.rb'
|
||||
InspecPlugins::Sweeten::StreamingReporter
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Like any activator, the block above will only be called if needed. For Streaming Reporter plugins, the plugin system examines the `--reporter` argument, or the `reporter:` JSON config option, and looks for the activation name as a prefix. Multiple Reporter activations may occur if several different names match, though each activation will only occur once.
|
||||
|
||||
```bash
|
||||
you@machine $ inspec exec --reporter streaming_sweet # Your Reporter implementation is activated and executed
|
||||
you@machine $ inspec exec --reporter json # Your Reporter implementation is not activated
|
||||
```
|
||||
|
||||
### Implementation class for Streaming Reporters
|
||||
|
||||
In your `streaming_reporter.rb`, you should begin by requesting the superclass from `Inspec.plugin`:
|
||||
|
||||
```ruby
|
||||
module InspecPlugins::Sweeten
|
||||
class StreamingReporter < Inspec.plugin(2, :streaming_reporter)
|
||||
RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending
|
||||
|
||||
def initialize output
|
||||
@output = output
|
||||
end
|
||||
|
||||
def example_passed notification # ExampleNotification
|
||||
# some logic to run on passing test
|
||||
end
|
||||
|
||||
def example_failed notification # FailedExampleNotification
|
||||
# some logic to run on failing test
|
||||
end
|
||||
|
||||
def example_pending notification # ExampleNotification
|
||||
# some logic to run on pending test
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Implementing your Streaming Reporter
|
||||
|
||||
A streaming reporter is a custom RSpec formatter which is used as an InSpec plugin. And it can be used for performing operations real-time using RSpec formatter methods like `example_passed`, `example_failed` and `example_pending`. Being an RSpec formatter, the method needs to be registered with `RSpec::Core::Formatters`.
|
||||
|
||||
This tutorial on [How to write RSpec formatters from Scratch](https://ieftimov.com/post/how-to-write-rspec-formatters-from-scratch/) will come handy.
|
||||
|
||||
## Implementing Input Plugins
|
||||
|
||||
Input plugins provide values for Chef InSpec Inputs - the parameters you can place within profile control code.
|
||||
|
|
|
@ -367,7 +367,11 @@ module Inspec
|
|||
.find_activators(plugin_type: :reporter)\
|
||||
.map(&:activator_name).map(&:to_s)
|
||||
|
||||
valid_types = rspec_built_in_formatters + inspec_reporters_that_are_not_yet_plugins + plugin_reporters
|
||||
streaming_reporters = Inspec::Plugin::V2::Registry.instance\
|
||||
.find_activators(plugin_type: :streaming_reporter)\
|
||||
.map(&:activator_name).map(&:to_s)
|
||||
|
||||
valid_types = rspec_built_in_formatters + inspec_reporters_that_are_not_yet_plugins + plugin_reporters + streaming_reporters
|
||||
|
||||
reporters.each do |reporter_name, reporter_config|
|
||||
raise NotImplementedError, "'#{reporter_name}' is not a valid reporter type." unless valid_types.include?(reporter_name)
|
||||
|
|
10
lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb
Normal file
10
lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
module Inspec::Plugin::V2::PluginType
|
||||
class StreamingReporter < Inspec::Plugin::V2::PluginBase # TBD Superclass may need to change
|
||||
register_plugin_type(:streaming_reporter)
|
||||
|
||||
#====================================================================#
|
||||
# StreamingReporter plugin type API
|
||||
#====================================================================#
|
||||
# Implementation classes must implement these methods.
|
||||
end
|
||||
end
|
|
@ -123,6 +123,8 @@ module Inspec
|
|||
def set_optional_formatters
|
||||
return if @conf["reporter"].nil?
|
||||
|
||||
# This is a slightly modified version of the default RSpec JSON formatter
|
||||
# No one in their right mind should be using this because we have a much better JSON reporter - named "json"
|
||||
if @conf["reporter"].key?("json-rspec")
|
||||
# We cannot pass in a nil output path. Rspec only accepts a valid string or a IO object.
|
||||
if @conf["reporter"]["json-rspec"]&.[]("file").nil?
|
||||
|
@ -133,6 +135,7 @@ module Inspec
|
|||
@conf["reporter"].delete("json-rspec")
|
||||
end
|
||||
|
||||
# These are built-in to rspec
|
||||
formats = @conf["reporter"].select { |k, _v| %w{documentation progress html}.include?(k) }
|
||||
formats.each do |k, v|
|
||||
# We cannot pass in a nil output path. Rspec only accepts a valid string or a IO object.
|
||||
|
@ -143,6 +146,33 @@ module Inspec
|
|||
end
|
||||
@conf["reporter"].delete(k)
|
||||
end
|
||||
|
||||
# Here we need to look for reporter names in the reporter option that
|
||||
# are names of streaming reporter plugins. We load them, then tell RSpec to add them as formatters.
|
||||
# They will have already been detected at this point (see v2_loader.load_all in cli.rb)
|
||||
# but they will not be activated activated at this point.
|
||||
# then list all plugins by type by name
|
||||
reg = Inspec::Plugin::V2::Registry.instance
|
||||
streaming_reporters = reg\
|
||||
.find_activators(plugin_type: :streaming_reporter)\
|
||||
.map(&:activator_name).map(&:to_s)
|
||||
|
||||
@conf["reporter"].each do |streaming_reporter_name, file_target|
|
||||
# It could be a non-streaming reporter
|
||||
next unless streaming_reporters.include? streaming_reporter_name
|
||||
|
||||
# Activate the plugin so the formatter ID gets registered with RSpec, presumably
|
||||
activator = reg.find_activator(plugin_type: :streaming_reporter, activator_name: streaming_reporter_name.to_sym)
|
||||
activator.activate!
|
||||
|
||||
# We cannot pass in a nil output path. Rspec only accepts a valid string or a IO object.
|
||||
if file_target&.[]("file").nil?
|
||||
RSpec.configuration.add_formatter(activator.implementation_class)
|
||||
else
|
||||
RSpec.configuration.add_formatter(activator.implementation_class, file_target["file"])
|
||||
end
|
||||
@conf["reporter"].delete(streaming_reporter_name)
|
||||
end
|
||||
end
|
||||
|
||||
# Configure the output formatter and stream to be used with RSpec.
|
||||
|
|
28
test/fixtures/plugins/inspec-streamer-bang/README.md
vendored
Normal file
28
test/fixtures/plugins/inspec-streamer-bang/README.md
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
# StreamerBang Plugin
|
||||
|
||||
This plugin was generated by `inspec init plugin`, and apparently the author, 'Progress Chef InSpec Team', did not update the README.
|
||||
|
||||
## To Install This Plugin
|
||||
|
||||
Assuming it has been published to RubyGems, you can install this gem using:
|
||||
|
||||
```
|
||||
you@machine $ inspec plugin install inspec-streamer-bang
|
||||
```
|
||||
|
||||
## What This Plugin Does
|
||||
|
||||
No idea.
|
||||
|
||||
## Developing This Plugin
|
||||
|
||||
The generated plugin contains everything a real-world, industrial grade plugin would have, including:
|
||||
|
||||
* an (possibly incomplete) implementation of one or more InSpec Plugin Types
|
||||
* documentation (you are reading it now)
|
||||
* tests, at the unit and functional level
|
||||
* a .gemspec, for packaging and publishing it as a gem
|
||||
* a Gemfile, for managing its dependencies
|
||||
* a Rakefile, for running development tasks
|
||||
* Rubocop linting support for using the base InSpec project rubocop.yml (See Rakefile)
|
||||
|
14
test/fixtures/plugins/inspec-streamer-bang/lib/inspec-streamer-bang.rb
vendored
Normal file
14
test/fixtures/plugins/inspec-streamer-bang/lib/inspec-streamer-bang.rb
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
# This file is known as the "entry point."
|
||||
# This is the file InSpec will try to load if it
|
||||
# thinks your plugin is installed.
|
||||
|
||||
# The *only* thing this file should do is setup the
|
||||
# load path, then load the plugin definition file.
|
||||
|
||||
# Next two lines simply add the path of the gem to the load path.
|
||||
# This is not needed when being loaded as a gem; but when doing
|
||||
# plugin development, you may need it. Either way, it's harmless.
|
||||
libdir = File.dirname(__FILE__)
|
||||
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
||||
|
||||
require "inspec-streamer-bang/plugin"
|
31
test/fixtures/plugins/inspec-streamer-bang/lib/inspec-streamer-bang/plugin.rb
vendored
Normal file
31
test/fixtures/plugins/inspec-streamer-bang/lib/inspec-streamer-bang/plugin.rb
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Plugin Definition file
|
||||
# The purpose of this file is to declare to InSpec what plugin_types (capabilities)
|
||||
# are included in this plugin, and provide activator that will load them as needed.
|
||||
|
||||
# It is important that this file load successfully and *quickly*.
|
||||
# Your plugin's functionality may never be used on this InSpec run; so we keep things
|
||||
# fast and light by only loading heavy things when they are needed.
|
||||
|
||||
# Presumably this is light
|
||||
require "inspec-streamer-bang/version"
|
||||
|
||||
# The InspecPlugins namespace is where all plugins should declare themselves.
|
||||
# The "Inspec" capitalization is used throughout the InSpec source code; yes, it's
|
||||
# strange.
|
||||
module InspecPlugins
|
||||
module StreamerBang
|
||||
class Plugin < ::Inspec.plugin(2)
|
||||
plugin_name :"inspec-streamer-bang"
|
||||
|
||||
streaming_reporter :bang do
|
||||
# Calling this activator doesn't mean the subcommand is being executed - just
|
||||
# that we should be ready to do so. So, load the file that defines the
|
||||
# functionality.
|
||||
require "inspec-streamer-bang/streaming_reporter"
|
||||
|
||||
InspecPlugins::StreamerBang::StreamingReporter
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
21
test/fixtures/plugins/inspec-streamer-bang/lib/inspec-streamer-bang/streaming_reporter.rb
vendored
Normal file
21
test/fixtures/plugins/inspec-streamer-bang/lib/inspec-streamer-bang/streaming_reporter.rb
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
module InspecPlugins::StreamerBang
|
||||
class StreamingReporter < Inspec.plugin(2, :streaming_reporter)
|
||||
RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending
|
||||
|
||||
def initialize(output)
|
||||
@output = output
|
||||
end
|
||||
|
||||
def example_passed(notification) # ExampleNotification
|
||||
@output << "!"
|
||||
end
|
||||
|
||||
def example_failed(notification) # FailedExampleNotification
|
||||
@output << "F"
|
||||
end
|
||||
|
||||
def example_pending(notification) # ExampleNotification
|
||||
@output << "*"
|
||||
end
|
||||
end
|
||||
end
|
8
test/fixtures/plugins/inspec-streamer-bang/lib/inspec-streamer-bang/version.rb
vendored
Normal file
8
test/fixtures/plugins/inspec-streamer-bang/lib/inspec-streamer-bang/version.rb
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
# This file simply makes it easier for CI engines to update
|
||||
# the version stamp, and provide a clean way for the gemspec
|
||||
# to learn the current version.
|
||||
module InspecPlugins
|
||||
module StreamerBang
|
||||
VERSION = "0.1.0".freeze
|
||||
end
|
||||
end
|
|
@ -183,6 +183,23 @@ describe "disable plugin usage message integration" do
|
|||
end
|
||||
end
|
||||
|
||||
#=========================================================================================#
|
||||
# Streaming reporter Plugin Support
|
||||
#=========================================================================================#
|
||||
|
||||
describe "Streaming-reporter plugin type support" do
|
||||
include PluginFunctionalHelper
|
||||
|
||||
let(:fixture_path) { File.join(profile_path, "basic_profile") }
|
||||
let(:streaming_reporter_plugin_path) { File.join(mock_path, "plugins", "inspec-streamer-bang", "lib", "inspec-streamer-bang.rb") }
|
||||
let(:run_result) { run_inspec_with_plugin("exec #{fixture_path} --reporter bang", plugin_path: streaming_reporter_plugin_path) }
|
||||
|
||||
it "runs the streaming reporter plugin type successfully" do
|
||||
_(run_result.stderr).must_be_empty
|
||||
_(run_result.exit_status).must_equal 0
|
||||
end
|
||||
end
|
||||
|
||||
#=========================================================================================#
|
||||
# DSL Plugin Support
|
||||
#=========================================================================================#
|
||||
|
|
Loading…
Reference in a new issue