diff --git a/.fixtures.yml b/.fixtures.yml index f2409b9..2d45c82 100644 --- a/.fixtures.yml +++ b/.fixtures.yml @@ -9,5 +9,6 @@ fixtures: compliance_markup: https://github.com/simp/pupmod-simp-compliance_markup.git symlinks: simpkv: "#{source_dir}" - test_plugins: "#{File.join(source_dir, 'spec', 'support', 'modules', 'test_plugins')}" + test_plugins1: "#{File.join(source_dir, 'spec', 'support', 'modules', 'test_plugins1')}" + test_plugins2: "#{File.join(source_dir, 'spec', 'support', 'modules', 'test_plugins2')}" simpkv_test: "#{File.join(source_dir, 'spec', 'support', 'modules', 'simpkv_test')}" diff --git a/CHANGELOG b/CHANGELOG index 053d378..0e26479 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,9 +15,17 @@ function with a 'global' Boolean option. - Global keys are now specified by setting 'global' to true in lieu of setting 'environment' to ''. - - Restrict letter characters used in key and folder names to be - lowercase. + - Changed the key and folder name specification to restrict letter + characters to lowercase. - Change required for the LDAP plugin. + - Changed the plugin configuration API + - Configuration has been split out into its own method, instead of being + done in the plugin constructor. + - This minimal change simplifies unit testing of configuration of complex + plugins. + - Fixed the mechanism a plugin uses to advertise its type. + - Plugin type is now determined from its filename. + - Previous mechanism did not work when when multiple plugins were used. - Added - More detailed plugin exception reporting in order to pinpoint plugin logic problems. diff --git a/docs/Design_Prototype2.md b/docs/Design_Prototype2.md index a4751e2..2c6b38e 100644 --- a/docs/Design_Prototype2.md +++ b/docs/Design_Prototype2.md @@ -168,25 +168,27 @@ simpkv must provide a backend plugin adapter that simpkv must supply a backend plugin API that provides - * Public API method signatures, including the constructor and a - method that reports the plugin type (typically backend it supports) - * Description of any universal plugin options that must be supported - * Ability to specify plugin-specific options + * Public API method signatures, including the constructor and `configure()` + method + * Description of any universal plugin configuration options that must be + supported + * Ability to specify plugin-specific configuration options * Explicit policy on error handling (how to report errors, what information the messages should contain for plugin proper identification, whether exceptions are allowed) - * Details on the code structure required for prevention of cross-environment - contamination + * Details on the code structure required for prevention of cross-Puppet- + environment contamination * Documentation requirements * Testing requirements Each plugin must conform to the plugin API and satisfy the following general requirements: -* All plugins must be unique. +* All plugin files, potentially from multiple modules, must be uniquely named. - * Plugin Ruby files can be named the same in different modules, but - their reported plugin types must be unique. + * The plugin type is derived from its filename: \_plugin.rb. + * Only one plugin of the same name will be loaded and a warning will be + emitted for all other conflicting plugin files. * All plugins must allow multiple instances of the plugin to be instantiated and used in a single catalog compile. @@ -298,7 +300,7 @@ general requirements: This is a placeholder for miscellaneous, additional simpkv requirements to be addressed, once it moves beyond the prototype stage. -* simpkv must provide a plugin for a remote key/value store, such as Consul +* simpkv must provide a plugin for a remote key/value store, such as LDAP * simpkv must support audit operations on a key/value store * Auditing information to be provided must include: @@ -318,7 +320,7 @@ to be addressed, once it moves beyond the prototype stage. * simpkv should provide a script to import existing `simplib::passgen()` passwords stored in the puppetserver cache directory, PKI secrets stored in `/var/simp/environments`, and Kerberos secrets - stored in `/var/simp/environments` to a backend. + stored in `/var/simp/environments` into a backend. * simpkv local file backend must encrypt each file it maintains. * simpkv local file backend must ensure multi-process-safe `put`, `delete`, and `deletetree` operations on a _plugin.rb and address the FIXMEs +# Copy this file to /lib/puppet_x/simpkv/_plugin.rb, +# read all the documentation in this file and address the FIXMEs. # +############################################################################### +# SIMPKV PLUGIN REQUIREMENTS +# - The plugin type derived from the plugin's base filename must be unique +# over **all** plugins loaded. +# - The simpkv adapter will only load the first plugin it finds of any given +# type. +# - The simpkv adapter will emit a warning when multiple plugin files for the +# same type are detected. +# +# - The plugin code must implement the API in this template. +# +# - The plugin code must protect from cross-puppet-environment contamination. +# Different versions of the module containing this plugin may be loaded +# into the puppetserver at the same time. So, unlike normal Ruby library +# code for which only one version will be loaded at a time (e.g., gems +# installed in the puppetserver), you have to explicitly design this plugin +# code to prevent cross-environment-contamination. This is why the plugin +# architecture requires this class to be anonymous and loads it appropriately. +# You must provide similar protections for any supporting Ruby code that you +# package in the module (e.g., a separate connector class). +# +# - The plugin code must allow multiple instances to be instantiated and run +# concurrently. +# +# - The plugin code is responsible for executing any appropriate retry logic +# on failed backend operations. +# +# - The plugin code must protect itself from hung operations. +# +# - When accessing the backend in the put(), get(), ... methods, the plugin code +# should catch exceptions, convert them to meaningful error messages and then +# return the failed status in its public API. +############################################################################### + -# DO NOT CHANGE THIS LINE!!!! # Each plugin **MUST** be an anonymous class accessible only through # a `plugin_class` local variable. +# DO NOT CHANGE THE LINE BELOW!!!! plugin_class = Class.new do - # Reminder: Do **NOT** try to set constants in this Class.new block. - # They don't do what you expect (are not accessible within - # any class methods) and pollute the Object namespace. + # WARNING: + # In typical Ruby code, using constants and class methods is quite normal. + # Unfortunately, you cannot use constants or class methods in an anonymous + # class, as they will be added to the Class Object, itself, and will not be + # available to the anonymous class. In other words, you will be tearing your + # hair out trying to figure out why normal Ruby code does not work here! ###### Public Plugin API ###### - # @return String. backend type - def self.type - # This is the value that will be in the 'type' attribute of a configuration - # block for this plugin. The simpkv adapter uses to select the plugin class to - # use in order to create a plugin instance. - # This **MUST** be unique across all loaded plugins. Only the first - # plugin of a particular type will be loaded! - 'FIXME' - end + # Construct an instance of this plugin setting its instance name + # + # @param name Name to ascribe to this plugin instance + # + def initialize(name) + # save this off, because the simpkv adapter will access it through a getter + # (defined below), when constructing log messages + @name = name + # You can use the Puppet object for logging + Puppet.debug("#{@name} simpkv plugin constructed") + end - # Construct an instance of this plugin using global and plugin-specific + # Configure this plugin instance using global and plugin-specific # configuration found in options # # FIXME: The description below is informational for you as a developer. @@ -65,21 +105,17 @@ def self.type # } # } # - # @param name Name to ascribe to this plugin instance # @param options Hash of global simpkv and backend-specific options # @raise RuntimeError if any required configuration is missing from options # or this object can't set up any stateful objects it needs to do its work # (e.g., file directory, connection to a backend) - def initialize(name, options) - # save this off, because the simpkv adapter will access it through a getter - # (defined below) when constructing log messages - @name = name + def configure(options) # FIXME: insert validation and set up code here # Be sure to create 'globals' and 'environments' sub-folders off of the # root directory. - Puppet.debug("#{@name} simpkv plugin constructed") + Puppet.debug("#{@name} simpkv plugin configured") end # @return unique identifier assigned to this plugin instance @@ -87,21 +123,21 @@ def name @name end - # The remaining methods in this API map one-for-one to those in # simpkv's Puppet function API. # - # IMPORTANT NOTES: + # IMPORTANT API NOTES: # - # - An instance of this plugin class will persist through a single catalog run. + # - An instance of this plugin class will persist through a single catalog + # run. # - Other instances of this plugin class may be running concurrently in # the same process. # # * Make sure your code is multi-thread safe if you are using any # mechanisms that would cause concurrency problems! # - # - All values persisted and returned are Strings. Other software in the - # simpkv function chain is responsible for serializing non-String + # - All key values persisted and returned are Strings. Other software in + # the simpkv function chain is responsible for serializing non-String # values into Strings for plugins to persist and then deserializing # Strings retrieved by plugins back into objects. # @@ -121,8 +157,12 @@ def name # >> to create useful error messages! << # # - If your plugin connects to an external service, you are strongly - # encouraged to build retry logic and timeouts into your backend + # encouraged to build timeouts and retry logic into your backend # operations. + # + # * The simpkv adapter does not currently protect against hung operations. + # * Only you have domain knowledge to know when a connection is hung + # and when a retry of a failed operaton is appropriate. # Deletes a `key` from the configured backend. # @@ -134,7 +174,7 @@ def name # def delete(key) - # FIXME: insert code that connects to the backend an affects the delete + # FIXME: insert code that connects to the backend and affects the delete # operation # # - This delete should be done atomically @@ -155,7 +195,7 @@ def delete(key) # def deletetree(keydir) - # FIXME: insert code that connects to the backend an affects the deletetree + # FIXME: insert code that connects to the backend and affects the deletetree # operation # # - If supported, this deletetree should be done atomically. If not, @@ -179,7 +219,7 @@ def deletetree(keydir) # def exists(key) - # FIXME: insert code that connects to the backend an affects the exists + # FIXME: insert code that connects to the backend and affects the exists # operation # # - Convert any exceptions into a failed status result with a meaningful @@ -200,7 +240,7 @@ def exists(key) # def get(key) - # FIXME: insert code that connects to the backend an affects the get + # FIXME: insert code that connects to the backend and affects the get # operation # # - If possible, this get should be done atomically @@ -233,7 +273,7 @@ def get(key) # def list(keydir) - # FIXME: insert code that connects to the backend an affects the list + # FIXME: insert code that connects to the backend and affects the list # operation # # - Convert any exceptions into a failed status result with a meaningful @@ -254,7 +294,7 @@ def list(keydir) # def put(key, value) - # FIXME: insert code that connects to the backend an affects the put + # FIXME: insert code that connects to the backend and affects the put # operation # # - This delete should be done atomically diff --git a/lib/puppet_x/simpkv/simpkv.rb b/lib/puppet_x/simpkv/simpkv.rb index 731ff60..1985eec 100644 --- a/lib/puppet_x/simpkv/simpkv.rb +++ b/lib/puppet_x/simpkv/simpkv.rb @@ -18,12 +18,16 @@ require 'json' require 'pathname' - attr_accessor :plugin_classes, :plugin_instances + attr_accessor :plugin_info, :plugin_instances def initialize Puppet.debug('Constructing simpkv adapter from anonymous class') - @plugin_classes = {} # backend plugin classes; - # key = backend type returned by .type + @plugin_info = {} # backend plugin classes; + # key = backend type derived from the plugin base + # filename (_plugin.rb) + # value = { :class => , + # :source => + # } @plugin_instances = {} # backend plugin instances; # key = name assigned to the instance, / # supports multiple backend plugin instances per backend @@ -32,26 +36,23 @@ def initialize # # - Every file in modules/*/lib/puppet_x/simpkv/*_plugin.rb is assumed # to contain a simpkv backend plugin. + # - A field in the base filename of the plugin defines the plugin type, + # _.plugin.rb, and only the first plugin found for a + # type will be loaded. # - Each plugin file must contain an anonymous class that can be accessed # by a 'plugin_class' local variable. - # - Each plugin must provide the following methods, which are described - # in detail in plugin_template.rb: - # - Class methods: - # - type: Class method that returns the backend type - # - Instance methods: - # - initialize: constructor - # - name: return unique identifier assigned to the plugin instance - # - delete: delete key from the backend - # - deletetree: delete a folder from the backend - # - exists: check for existence of key in the backend - # - get: retrieve the value of a key in the backend - # - list: list the key/value pairs and sub-folders available in a - # folder in the backend - # - put: insert a key/value pair into the backend - # - # NOTE: All backend plugins must return a unique value for .type(). - # Otherwise, only the Class object for last plugin with the same - # type will be stored in the plugin_classes Hash. + # - Each plugin must provide the following instance methods, which are + # described in detail in plugin_template.rb: + # - initialize: constructor + # - configure: Configure the plugin instance + # - name: return unique identifier assigned to the plugin instance + # - delete: delete key from the backend + # - deletetree: delete a folder from the backend + # - exists: check for existence of key in the backend + # - get: retrieve the value of a key in the backend + # - list: list the key/value pairs and sub-folders available in a + # folder in the backend + # - put: insert a key/value pair into the backend # modules_dir = File.dirname(File.dirname(File.dirname(File.dirname(File.dirname(__FILE__))))) plugin_glob = File.join(modules_dir, '*', 'lib', 'puppet_x', 'simpkv', '*_plugin.rb') @@ -65,12 +66,17 @@ def initialize begin plugin_class = nil self.instance_eval(File.read(filename), filename) - if @plugin_classes.has_key?(plugin_class.type) + plugin_type = File.basename(filename, '_plugin.rb') + if @plugin_info.has_key?(plugin_type) msg = "Skipping load of simpkv plugin from #{filename}: " + - "plugin type '#{plugin_class.type}' already loaded" + "plugin type '#{plugin_type}' already loaded from " + + @plugin_info[plugin_type][:source] Puppet.warning(msg) else - @plugin_classes[plugin_class.type] = plugin_class + @plugin_info[plugin_type] = { + :class => plugin_class, + :source => filename + } end rescue SyntaxError => e Puppet.warning("simpkv plugin from #{filename} failed to load: #{e.message}") @@ -83,7 +89,7 @@ def initialize # @return list of backend plugins (i.e. their types) that have successfully # loaded def backends - return plugin_classes.keys.sort + return plugin_info.keys.sort end # execute delete operation on the backend, after normalizing the key @@ -303,7 +309,6 @@ def filter_backtrace(backtrace) short_bt.reverse end - # @return prefix to be used in the path for global keys/folders def global_prefix 'globals' @@ -370,7 +375,7 @@ def plugin_instance(options) options['backends'].has_key?(options['backend']) && options['backends'][ options['backend'] ].has_key?('id') && options['backends'][ options['backend'] ].has_key?('type') && - plugin_classes.has_key?(options['backends'][ options['backend'] ]['type']) + plugin_info.has_key?(options['backends'][ options['backend'] ]['type']) ) raise("Malformed backend config in options=#{options}") end @@ -383,7 +388,8 @@ def plugin_instance(options) name = "#{type}/#{id}" unless plugin_instances.has_key?(name) begin - plugin_instances[name] = plugin_classes[type].new(name, options) + plugin_instances[name] = plugin_info[type][:class].new(name) + plugin_instances[name].configure(options) rescue Exception => e raise("Unable to construct '#{name}': #{e.message}") end diff --git a/spec/support/modules/test_plugins/lib/puppet_x/simpkv/failer_plugin.rb b/spec/support/modules/test_plugins1/lib/puppet_x/simpkv/failer_plugin.rb similarity index 87% rename from spec/support/modules/test_plugins/lib/puppet_x/simpkv/failer_plugin.rb rename to spec/support/modules/test_plugins1/lib/puppet_x/simpkv/failer_plugin.rb index e43dbe6..9651d4b 100644 --- a/spec/support/modules/test_plugins/lib/puppet_x/simpkv/failer_plugin.rb +++ b/spec/support/modules/test_plugins1/lib/puppet_x/simpkv/failer_plugin.rb @@ -1,6 +1,6 @@ # This is a bad-behaving plugin that will raise an exception # during a public plugin API method to support testing. It can -# also be configured to raise an exception in its constructor. +# also be configured to raise an exception in its configure(). # # Each plugin **MUST** be an anonymous class accessible only through @@ -9,13 +9,16 @@ ###### Public Plugin API ###### - # @return String. backend type - def self.type - 'failer' + # Construct an instance of this plugin setting its instance name + # + # @param name Name to ascribe to this plugin instance + # + def initialize(name) + @name = name + Puppet.debug("#{@name} simpkv plugin constructed") end - - # Construct an instance of this plugin using global and plugin-specific + # Configure this plugin instance using global and plugin-specific # configuration found in options # # The plugin-specific configuration will be found in @@ -26,17 +29,13 @@ def self.type # @raise RuntimeError if any required configuration is missing from options # or this object can't set up any stateful objects it needs to do its work # (e.g., file directory, connection to a backend) - def initialize(name, options) - # save this off, because the simpkv adapter will access it through a getter - # (defined below) when constructing log messages - @name = name - + def configure(options) backend = options['backend'] - if options['backends'][backend]['fail_constructor'] - raise('Constructor catastrophic failure') + if options['backends'][backend]['fail_configure'] + raise('configure() catastrophic failure') end - Puppet.debug("#{@name} simpkv plugin constructed") + Puppet.debug("#{@name} simpkv plugin configured") end # @return unique identifier assigned to this plugin instance diff --git a/spec/support/modules/test_plugins/lib/puppet_x/simpkv/malformed_plugin.rb b/spec/support/modules/test_plugins1/lib/puppet_x/simpkv/malformed_plugin.rb similarity index 100% rename from spec/support/modules/test_plugins/lib/puppet_x/simpkv/malformed_plugin.rb rename to spec/support/modules/test_plugins1/lib/puppet_x/simpkv/malformed_plugin.rb diff --git a/spec/support/modules/test_plugins/metadata.json b/spec/support/modules/test_plugins1/metadata.json similarity index 53% rename from spec/support/modules/test_plugins/metadata.json rename to spec/support/modules/test_plugins1/metadata.json index e793d29..460308a 100644 --- a/spec/support/modules/test_plugins/metadata.json +++ b/spec/support/modules/test_plugins1/metadata.json @@ -1,5 +1,5 @@ { - "name": "simp-test_plugins", + "name": "simp-test_plugins1", "version": "9.9.9", "author": "simp", "summary": "plugins for simpkv error testing", @@ -9,26 +9,10 @@ "issues_url": "https://simp-project.atlassian.net", "tags": [], "dependencies": [], - "operatingsystem_support": [ - { - "operatingsystem": "CentOS", - "operatingsystemrelease": [ - "6", - "7" - ] - }, - { - "operatingsystem": "RedHat", - "operatingsystemrelease": [ - "6", - "7" - ] - } - ], "requirements": [ { "name": "puppet", - "version_requirement": ">= 5.0.0 < 7.0.0" + "version_requirement": ">= 6.0.0" } ] } diff --git a/spec/support/modules/test_plugins/lib/puppet_x/simpkv/second_failer_plugin.rb b/spec/support/modules/test_plugins2/lib/puppet_x/simpkv/failer_plugin.rb similarity index 84% rename from spec/support/modules/test_plugins/lib/puppet_x/simpkv/second_failer_plugin.rb rename to spec/support/modules/test_plugins2/lib/puppet_x/simpkv/failer_plugin.rb index cc9e0b0..a94ec4f 100644 --- a/spec/support/modules/test_plugins/lib/puppet_x/simpkv/second_failer_plugin.rb +++ b/spec/support/modules/test_plugins2/lib/puppet_x/simpkv/failer_plugin.rb @@ -1,6 +1,6 @@ # This is conflicting version of failer_plugin.rb. It is used to verify -# only one plugin of a given type is loaded. It has the same type -# as failure_plugin.rb, but different error message. +# only one plugin of a given type is loaded. It has the same filename +# (type) as failure_plugin.rb, but different error messages. # Each plugin **MUST** be an anonymous class accessible only through # a `plugin_class` local variable. @@ -8,13 +8,16 @@ ###### Public Plugin API ###### - # @return String. backend type - def self.type - 'failer' + # Construct an instance of this plugin setting its instance name + # + # @param name Name to ascribe to this plugin instance + # + def initialize(name) + @name = name + Puppet.debug("#{@name} simpkv plugin constructed") end - - # Construct an instance of this plugin using global and plugin-specific + # Configure this plugin instance using global and plugin-specific # configuration found in options # # The plugin-specific configuration will be found in @@ -25,17 +28,13 @@ def self.type # @raise RuntimeError if any required configuration is missing from options # or this object can't set up any stateful objects it needs to do its work # (e.g., file directory, connection to a backend) - def initialize(name, options) - # save this off, because the simpkv adapter will access it through a getter - # (defined below) when constructing log messages - @name = name - + def configure(options) backend = options['backend'] - if options['backends'][backend]['fail_constructor'] - raise('Catastrophic failure in constructor') + if options['backends'][backend]['fail_configure'] + raise('Catastrophic failure in configure()') end - Puppet.debug("constructed #{@name} simpkv plugin") + Puppet.debug("configured #{@name} simpkv plugin") end # @return unique identifier assigned to this plugin instance diff --git a/spec/support/modules/test_plugins2/metadata.json b/spec/support/modules/test_plugins2/metadata.json new file mode 100644 index 0000000..1a1175c --- /dev/null +++ b/spec/support/modules/test_plugins2/metadata.json @@ -0,0 +1,18 @@ +{ + "name": "simp-test_plugins2", + "version": "9.9.9", + "author": "simp", + "summary": "plugins for simpkv error testing", + "license": "Apache-2.0", + "source": "https://github.com/simp/pupmod-simp-simpkv", + "project_page": "https://github.com/simp/pupmod-simp-simpkv", + "issues_url": "https://simp-project.atlassian.net", + "tags": [], + "dependencies": [], + "requirements": [ + { + "name": "puppet", + "version_requirement": ">= 6.0.0" + } + ] +} diff --git a/spec/unit/puppet_x/simpkv/file_plugin_spec.rb b/spec/unit/puppet_x/simpkv/file_plugin_spec.rb index 4b7ad9f..6b588d6 100644 --- a/spec/unit/puppet_x/simpkv/file_plugin_spec.rb +++ b/spec/unit/puppet_x/simpkv/file_plugin_spec.rb @@ -82,16 +82,14 @@ def locked_key_file_operation(root_path, key, value, &block) FileUtils.remove_entry_secure(@tmpdir) end - context 'type' do - it "class.type should return 'file'" do - expect(plugin_class.type).to eq 'file' + context 'configure' do + before(:each) do + @plugin = plugin_class.new(@plugin_name) end - end - context 'constructor' do context 'success cases' do it 'should create the root_path tree when none exists' do - expect{ plugin_class.new(@plugin_name, @options) }.to_not raise_error + expect{ @plugin.configure(@options) }.to_not raise_error expect( Dir.exist?(@root_path) ).to be true expect( Dir.exist?(File.join(@root_path, 'globals')) ).to be true expect( Dir.exist?(File.join(@root_path, 'environments')) ).to be true @@ -99,18 +97,18 @@ def locked_key_file_operation(root_path, key, value, &block) it 'should not fail if the root_path tree exists' do FileUtils.mkdir_p(@root_path) - expect { plugin_class.new(@plugin_name, @options) }.to_not raise_error + expect{ @plugin.configure(@options) }.to_not raise_error end end context 'error cases' do it 'should fail when options is not a Hash' do - expect { plugin_class.new(@plugin_name, 'oops') }. + expect { @plugin.configure('oops') }. to raise_error(/Plugin misconfigured/) end it "should fail when options missing 'backend' key" do - expect { plugin_class.new(@plugin_name, {} ) }. + expect { @plugin.configure({}) }. to raise_error(/Plugin misconfigured/) end @@ -118,8 +116,8 @@ def locked_key_file_operation(root_path, key, value, &block) options = { 'backend' => 'test' } - expect { plugin_class.new(@plugin_name, options) }. - to raise_error(/Plugin misconfigured: {.*backend.*}/) + expect { @plugin.configure({}) }. + to raise_error(/Plugin misconfigured: {}/) end it "should fail when options 'backends' key is not a Hash" do @@ -127,7 +125,7 @@ def locked_key_file_operation(root_path, key, value, &block) 'backend' => 'test', 'backends' => 'oops' } - expect { plugin_class.new(@plugin_name, options) }. + expect { @plugin.configure(options) }. to raise_error(/Plugin misconfigured/) end @@ -138,7 +136,7 @@ def locked_key_file_operation(root_path, key, value, &block) 'test1' => { 'id' => 'test', 'type' => 'consul'} } } - expect { plugin_class.new(@plugin_name, options) }. + expect { @plugin.configure(options) }. to raise_error(/Plugin misconfigured/) end @@ -150,7 +148,7 @@ def locked_key_file_operation(root_path, key, value, &block) 'test' => {} } } - expect { plugin_class.new(@plugin_name, options) }. + expect { @plugin.configure(options) }. to raise_error(/Plugin misconfigured/) end @@ -162,7 +160,7 @@ def locked_key_file_operation(root_path, key, value, &block) 'test' => { 'id' => 'test' } } } - expect { plugin_class.new(@plugin_name, options) }. + expect { @plugin.configure(options) }. to raise_error(/Plugin misconfigured/) end @@ -174,7 +172,7 @@ def locked_key_file_operation(root_path, key, value, &block) 'test' => { 'id' => 'test', 'type' => 'filex' } } } - expect { plugin_class.new(@plugin_name, options) }. + expect { @plugin.configure(options) }. to raise_error(/Plugin misconfigured/) end @@ -195,15 +193,16 @@ def locked_key_file_operation(root_path, key, value, &block) allow(FileUtils).to receive(:mkdir_p).with('/can/not/be/created'). and_raise(Errno::EACCES, 'Permission denied') - expect { plugin_class.new(@plugin_name, options) }. + expect { @plugin.configure(options) }. to raise_error(/Unable to create configured root path/) end end end - context 'public API' do + context 'public simpkv operations API' do before(:each) do - @plugin = plugin_class.new(@plugin_name, @options) + @plugin = plugin_class.new(@plugin_name) + @plugin.configure(@options) end describe 'delete' do @@ -488,7 +487,8 @@ def locked_key_file_operation(root_path, key, value, &block) context 'internal methods' do before(:each) do - @plugin = plugin_class.new(@plugin_name, @options) + @plugin = plugin_class.new(@plugin_name) + @plugin.configure(@options) end describe 'ensure_folder_path' do diff --git a/spec/unit/puppet_x/simpkv/simpkv_spec.rb b/spec/unit/puppet_x/simpkv/simpkv_spec.rb index 9003dbe..c690d1c 100644 --- a/spec/unit/puppet_x/simpkv/simpkv_spec.rb +++ b/spec/unit/puppet_x/simpkv/simpkv_spec.rb @@ -29,9 +29,9 @@ # will use failer plugin for catastrophic error cases, because # it is badly behaved and raises exceptions on all operations 'test_failer' => { - 'id' => 'test', - 'type' => 'failer', - 'fail_constructor' => false # true = raise in constructor + 'id' => 'test', + 'type' => 'failer', + 'fail_configure' => false # true = raise in configure() }, # will use file plugin for non-catastrophic test cases 'test_file' => { @@ -47,9 +47,9 @@ 'backend' => 'test_failer', 'backends' => { 'test_failer' => { - 'id' => 'test', - 'type' => 'failer', - 'fail_constructor' => true # true = raise in constructor + 'id' => 'test', + 'type' => 'failer', + 'fail_configure' => true # true = raise in configure() } } } @@ -64,8 +64,10 @@ it 'should load valid plugin classes' do expect{ simp_simpkv_adapter_class.new }.to_not raise_error adapter = simp_simpkv_adapter_class.new - expect( adapter.plugin_classes ).to_not be_empty - expect( adapter.plugin_classes.keys.include?('file') ).to be true + expect( adapter.plugin_info ).to_not be_empty + expect( adapter.plugin_info.keys.include?('file') ).to be true + expect( adapter.plugin_info.keys.include?('failer') ).to be true + expect( adapter.plugin_info.keys.include?('malformed') ).to be false end it 'should discard a plugin class with malformed Ruby' do @@ -159,7 +161,7 @@ it 'should create an instance when config is correct' do instance = @adapter.plugin_instance(@options_file) - file_class_id = @adapter.plugin_classes['file'].to_s + file_class_id = @adapter.plugin_info['file'][:class].to_s expect( instance.name ).to eq 'file/test' expect( instance.to_s ).to match file_class_id end @@ -308,7 +310,8 @@ # create our own file plugin instance so we can manipulate key/store # independent of the simpkv adapter - @plugin = @adapter.plugin_classes['file'].new('other', @options_file) + @plugin = @adapter.plugin_info['file'][:class].new('other') + @plugin.configure(@options_file) end let(:key) { 'my/test/key' }