Templatize everything for InSpec plugins

Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
Clinton Wolfe 2018-11-25 01:57:51 -05:00
parent 5343c217b0
commit 731b0705f5
7 changed files with 104 additions and 81 deletions

View file

@ -1,7 +1,6 @@
# encoding: utf-8
require_relative 'renderer'
require 'byebug'
module InspecPlugins
module Init
@ -17,8 +16,9 @@ module InspecPlugins
option :description, type: :string, default: '', desc: 'Multi-paragraph description of the plugin.'
option :summary, type: :string, default: 'A plugin with a default summary', desc: 'One-line summary of your plugin'
option :license_name, type: :string, default: 'Apache-2.0', desc: 'The name of a license'
option :hook, type: :array, default: ['cli_command:my_command'], desc: 'A list of plugin hooks, in the form type1:name1, type2:name2, etc'
# These vars have calulated defaults
# These vars have calculated defaults
option :homepage, type: :string, default: '', desc: 'A URL for your project, often a GitHub link'
option :module_name, type: :string, default: '', desc: 'Module Name for your plugin package. Will change plugin name to CamelCase by default.'
@ -76,7 +76,29 @@ module InspecPlugins
license_name: options[:license_name],
license_text: fetch_license_text(options[:license_name]),
copyright: options[:copyright],
}
activators: options[:activators],
}.merge(parse_hook_option(options[:hook]))
end
def parse_hook_option(raw_option)
hooks_by_type = {}
raw_option.each do |entry|
parts = entry.split(':')
type = parts.first.to_sym
name = parts.last
if hooks_by_type.key?(type)
ui.error 'Inspec plugin generate can currently only generate one hook of each type'
ui.exit(:usage_error)
end
hooks_by_type[type] = name
end
vars = { hooks: hooks_by_type }
if hooks_by_type.key?(:cli_command)
vars[:command_name_dashes] = hooks_by_type[:cli_command].tr('_', '-')
vars[:command_name_snake] = hooks_by_type[:cli_command].tr('-', '_')
end
vars
end
def fetch_license_text(license_name)

View file

@ -2,7 +2,7 @@
require 'inspec/resource'
module InspecPlugins::ResourceLister
module InspecPlugins::<%= module_name %>
# This class will provide the actual CLI implementation.
# Its superclass is provided by another call to Inspec.plugin,
# this time with two args. The first arg specifies we are requesting
@ -15,56 +15,50 @@ module InspecPlugins::ResourceLister
# commands, usage information, and options, use the Thor documentation.
class CliCommand < Inspec.plugin(2, :cli_command)
# This isn't provided by Thor, but is needed by InSpec so that it can
# register the subcommand. Args are a usage message, and a short decription.
# register the top-level subcommand. That is to say, subcommand_desc
# makes `inspec <%= command_name_dashes %> ...` work. Args to sub_command_desc are
# a usage message, and a short decription.
# These will appear when someone has installed the plugin, and then they
# run `inspec help`.
subcommand_desc 'list-resources [COMMAND]', 'List resources that InSpec finds.'
# Note: if you want your command (or subcommand) to have dashes in it,
# use underscores where you want a dash, and Thor will convert them.
# Thor will fail to find a command that is directly named with dashes.
subcommand_desc '<%= command_name_snake %> [COMMAND]', 'Your Usage Message Here'
# The usual rhythm for a Thor CLI file is description, options, command method.
# Thor just has you call DSL methods in sequence prior to each command.
# Let's make a command, 'core', that lists all of the resources included with InSpec.
# First, provide a usage / description. This will appear in `inspec help list-resources`.
desc 'core [OPTIONS]', 'List resources that are included with InSpec.'
# Let's make a command, 'do_something'. This will then be available
# as `inspec <%= command_name_dashes %> do-something
# (Change this method name to be something sensible for your plugin.)
# Let's include an option, -s, to summarize the list.
# First, provide a usage / description. This will appear
# in `inspec help <%= command_name_dashes %>`.
# As this is a usage message, you should write the command as it should appear
# to the user (if you want it to have dashes, use dashes)
desc 'do-something WHAT [OPTIONS]', 'Does something'
# Let's include an option, -s, to summarize
# Refer to the Thors docs; there is a lot you can do here.
option :summary, desc: 'Include a total at the bottom', \
type: :boolean, default: true, aliases: [:s]
# OK, now the actual method itself. If you provide params, you're telling Thor that
# you accept CLI arguments after all options have been consumed. Let's accept a
# pattern, assumed to be a wildcard substring. If we provide a default, the CLI arg becomes optional.
def core(pattern = /.+/)
# The code here will *only* be executed if someone actually runs
# `inspec list-resources core`. So, we can lazily wait to load
# expensive things here. However, InSpec has in fact already
# loaded the Resources, so we don't have anything to load.
# you accept CLI arguments after all options have been consumed.
# Note again that the method name has an underscore, but when invoked
# on the CLI, use a dash.
def do_something(what = 'nothing')
# The code here will *only* be executed if someone actually
# runs `inspec <%= command_name_dashes %> do-something`.
# If we were passed a CLI arg, wrap the arg in Regexp matchers so
# we will match anywhere in the name.
unless pattern == /.+/
pattern = Regexp.new('.*' + pattern + '.*')
end
# `what` will be the command line arg
# `options` will be a hash of CLI option values
# This gets a bit into InSpec innards; but this is simply a Hash.
registry = Inspec::Resource.default_registry
resource_names = registry.keys.grep(pattern).sort
# Talk to the user using the `ui` object (see Inspec::UI)
# ui.error('Whoops!')
# One day we'll have nice UI support.
resource_names.each { |name| puts name }
if options[:summary]
puts '-' * 30
puts "#{resource_names.count} resources total"
end
ui.warning('This is a generated plugin with a default implementation. Edit lib/<%= plugin_name %>/cli_command.rb to make it do what you want.')
ui.exit(:success) # or :usage_error
end
# A neat idea for future work would be to add another command, perhaps
# 'resource-pack', which examines a possibly remote resource pack and
# enumerates the resources it defines.
# Another idea might be to fetch a profile, and list the resources actually
# used in the controls of the profile, along with counts.
end
end

View file

@ -38,17 +38,17 @@ module InspecPlugins
# We'd like this to be list-resources, but Thor does not support hyphens
# see https://github.com/erikhuda/thor/pull/613
cli_command :listresources do
# Calling this hook doesn't mean list-resources is being executed - just
cli_command :<%= command_name_snake %> do
# Calling this hook 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.
# For example, InSpec will activate this hook when `inspec help` is
# executed, so that this plugin's usage message will be included in the help.
require 'inspec-resource-lister/cli_command'
require '<%= plugin_name %>/cli_command'
# Having loaded our functionality, return a class that will let the
# CLI engine tap into it.
InspecPlugins::ResourceLister::CliCommand
InspecPlugins::<%= module_name %>::CliCommand
end
end
end

View file

@ -1,10 +1,4 @@
# Functional Testing Area for Example Plugins
## What example tests are provided?
Here, since this is a CliCommand plugin, we provide one set of functional tests:
* inspec_resource_lister_test.rb - Runs `inspec resource-lister` in several circumstances, and verifies the output from the process.
# Functional Testing Area for Plugins
## What are functional tests?

View file

@ -1,11 +1,11 @@
# Unit Testing Area for Example Plugins
# Unit Testing Area for Plugins
## What Example Tests are Provided?
Here, since this is a CliCommand plugin, we provide two sets of unit tests:
## What Tests are Provided?
* plugin_def_test.rb - Would be useful in any plugin. Verifies that the plugin is properly detected and registered.
* cli_args_test.rb - Verifies that the expected commands are present, and that they have the expected options and args.
<% if hooks.key?(:cli_command) %>
* cli_args_test.rb - Tests the CLI options for a CLI Command plugin
<% end %>
## What are Unit Tests?

View file

@ -1,15 +1,15 @@
# This unit test performs some tests to verify that the command line options for
# inspec-resource-lister are correct.
# <%= plugin_name %> are correct.
# Include our test harness
require_relative '../helper'
# Load the class under test, the CliCommand definition.
require 'inspec-resource-lister/cli_command'
require '<%= plugin_name %>/cli_command'
# Because InSpec is a Spec-style test suite, we're going to use MiniTest::Spec
# here, for familiar look and feel. However, this isn't InSpec (or RSpec) code.
describe InspecPlugins::ResourceLister::CliCommand do
describe InspecPlugins::<%= module_name %>::CliCommand do
# When writing tests, you can use `let` to create variables that you
# can reference easily.
@ -17,14 +17,17 @@ describe InspecPlugins::ResourceLister::CliCommand do
# This is the CLI Command implementation class.
# It is a subclass of Thor, which is a CLI framework.
# This unit test file is mostly about verifying the Thor settings.
let(:cli_class) { InspecPlugins::ResourceLister::CliCommand }
let(:cli_class) { InspecPlugins::<%= module_name %>::CliCommand }
# This is a Hash of Structs that tells us details of options for the 'core' subcommand.
let(:core_options) { cli_class.all_commands['core'].options }
# From this point onwards, this test file assumes you did not rename
# the provided 'do_something' subcommand. As you implement your plugin,
# modify and add to the lines below to test your actual options.
# This is a Hash of Structs that tells us details of options for the 'do_something' subcommand.
let(:do_something_options) { cli_class.all_commands['do_something'].options }
# To group tests together, you can nest 'describe' in minitest/spec
# (that is discouraged in InSpec control code.)
describe 'the core command' do
describe 'the do-something subcommand' do
# Some tests through here use minitest Expectations, which attach to all
# Objects, and begin with 'must' (positive) or 'wont' (negative)
@ -32,32 +35,32 @@ describe InspecPlugins::ResourceLister::CliCommand do
# Option count OK?
it "should take one option" do
core_options.count.must_equal(1)
do_something_options.count.must_equal(1)
end
# Summary option
describe "the summary option" do
it "should be present" do
core_options.keys.must_include(:summary)
do_something_options.keys.must_include(:summary)
end
it "should have a description" do
core_options[:summary].description.wont_be_nil
do_something_options[:summary].description.wont_be_nil
end
it "should not be required" do
core_options[:summary].required.wont_equal(true)
do_something_options[:summary].required.wont_equal(true)
end
it "should have a single-letter alias" do
core_options[:summary].aliases.must_include(:s)
do_something_options[:summary].aliases.must_include(:s)
end
end
# Argument count
# The 'core' command takes one optional argument. According to the
# metaprogramming rules of Ruby, the core() method should thus have an
# The 'do-something' command takes one optional argument. According to the
# metaprogramming rules of Ruby, the do_something() method should thus have an
# arity of -1. See http://ruby-doc.org/core-2.5.1/Method.html#method-i-arity
# for how that number is caclulated.
it "should take one optional argument" do
cli_class.instance_method(:core).arity.must_equal(-1)
cli_class.instance_method(:do_something).arity.must_equal(-1)
end
end

View file

@ -58,30 +58,40 @@ class InitPluginCli < MiniTest::Test
/\#\s#{plugin}\s=>\s#{module_name}/,
/module\s#{module_name}/,
/plugin_name\s+:'#{plugin}'/,
# TODO: Default assumes cli
#cli_command :listresources
#require 'inspec-resource-lister/cli_command'
#InspecPlugins::ResourceLister::CliCommand
# Default assumes one cli hook
/cli_command :my_command/,
/require\s'#{plugin}\/cli_command'/,
/InspecPlugins::#{module_name}::CliCommand/,
],
File.join(plugin, 'lib', plugin, 'version.rb') => [
/module\s#{module_name}/,
],
File.join(plugin, 'lib', plugin, 'cli_command.rb') => [
# TODO: decide on hook approach
/module\sInspecPlugins::#{module_name}/,
/\#\smakes\s`inspec\smy-command\s\.\.\.`\swork\./,
/subcommand_desc\s'my_command\s\[COMMAND\]'/,
/\#\sas\s`inspec\smy-command\sdo-something/,
/\#\sin\s`inspec\shelp\smy-command`/,
/\#\sruns\s`inspec\smy-command\sdo-something`./,
/Edit\slib\/#{plugin}\/cli_command\.rb\sto\smake\sit\sdo/,
],
File.join(plugin, 'test', 'helper.rb') => [], # No interpolation
File.join(plugin, 'test', 'functional', 'README.md') => [], # No interpolation
File.join(plugin, 'test', 'functional', snake_case + '_test.rb') => [
# Whatever goes here
],
File.join(plugin, 'test', 'unit', 'plugin_def_test.rb') => [
/require\s'#{plugin}\/plugin'/,
/describe\sInspecPlugins::#{module_name}::Plugin\sdo/,
/let\(:plugin_name\)\s\{ \:'#{plugin}\' \}/,
/describe InspecPlugins::#{module_name}::Plugin\sdo/,
/let\(:plugin_name\) \{ \:'#{plugin}\' \}/,
],
File.join(plugin, 'test', 'unit', 'cli_args_test.rb') => [
# require 'inspec-resource-lister/cli_command'
# describe InspecPlugins::ResourceLister::CliCommand do
# let(:cli_class) { InspecPlugins::ResourceLister::CliCommand }
/require '#{plugin}\/cli_command'/,
/describe InspecPlugins::#{module_name}::CliCommand do/,
/let\(\:cli_class\) \{ InspecPlugins::#{module_name}::CliCommand \}/,
],
File.join(plugin, 'test', 'unit', 'README.md') => [
/cli_args_test\.rb/,
],
}.each do |path, regexen|
full_path = File.join(dir, path)