diff --git a/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb b/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb index b24b2b7d5..b63723018 100644 --- a/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +++ b/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb @@ -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) diff --git a/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/cli_command.rb b/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/cli_command.rb index ec95e03bf..0ab15dfb8 100644 --- a/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/cli_command.rb +++ b/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/cli_command.rb @@ -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 diff --git a/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/plugin.rb b/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/plugin.rb index af9e86662..2a4967741 100644 --- a/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/plugin.rb +++ b/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/plugin.rb @@ -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 diff --git a/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/functional/README.md b/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/functional/README.md index ffda3c291..30f4b9a26 100644 --- a/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/functional/README.md +++ b/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/functional/README.md @@ -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? diff --git a/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/unit/README.md b/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/unit/README.md index 81febd5a7..090b57494 100644 --- a/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/unit/README.md +++ b/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/unit/README.md @@ -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? diff --git a/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/unit/cli_args_test.rb b/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/unit/cli_args_test.rb index c2d892d84..01214f555 100644 --- a/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/unit/cli_args_test.rb +++ b/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/test/unit/cli_args_test.rb @@ -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 diff --git a/lib/plugins/inspec-init/test/functional/inspec_init_plugin_test.rb b/lib/plugins/inspec-init/test/functional/inspec_init_plugin_test.rb index 01d6b309e..75000d9ac 100644 --- a/lib/plugins/inspec-init/test/functional/inspec_init_plugin_test.rb +++ b/lib/plugins/inspec-init/test/functional/inspec_init_plugin_test.rb @@ -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)