diff options
author | John Keiser <jkeiser@opscode.com> | 2013-09-11 08:23:10 -0700 |
---|---|---|
committer | John Keiser <jkeiser@opscode.com> | 2013-09-11 08:23:10 -0700 |
commit | 9ee0d266b3292c7b1085c8f22cffbd298cf1e7ce (patch) | |
tree | 09e3573fcef0c10b0d4bc8ed052c987180349651 | |
parent | 191e22c743c4d6127c0aef0c6af8f412b3e105aa (diff) | |
parent | 41ea64371a277894a2ecafe3f8b77780edb4ea2a (diff) | |
download | mixlib-config-9ee0d266b3292c7b1085c8f22cffbd298cf1e7ce.tar.gz |
Add strict mode
-rw-r--r-- | lib/mixlib/config.rb | 105 | ||||
-rw-r--r-- | lib/mixlib/config/unknown_config_option_error.rb | 24 | ||||
-rw-r--r-- | spec/mixlib/config_spec.rb | 88 |
3 files changed, 195 insertions, 22 deletions
diff --git a/lib/mixlib/config.rb b/lib/mixlib/config.rb index 28a22b9..08b1da9 100644 --- a/lib/mixlib/config.rb +++ b/lib/mixlib/config.rb @@ -20,16 +20,17 @@ require 'mixlib/config/version' require 'mixlib/config/configurable' +require 'mixlib/config/unknown_config_option_error' module Mixlib module Config def self.extended(base) class << base; attr_accessor :configuration; end class << base; attr_accessor :configurables; end - class << base; attr_accessor :contexts; end + class << base; attr_accessor :config_contexts; end base.configuration = Hash.new base.configurables = Hash.new - base.contexts = Array.new + base.config_contexts = Array.new end # Loads a given ruby file, and runs instance_eval against it in the context of the current @@ -38,7 +39,7 @@ module Mixlib # Raises an IOError if the file cannot be found, or is not readable. # # === Parameters - # <string>:: A filename to read from + # filename<String>:: A filename to read from def from_file(filename) self.instance_eval(IO.read(filename), filename, 1) end @@ -46,7 +47,7 @@ module Mixlib # Pass Mixlib::Config.configure() a block, and it will yield itself # # === Parameters - # <block>:: A block that is called with self.configuration as the arugment. + # block<Block>:: A block that is called with self.configuration as the arugment. def configure(&block) block.call(self.configuration) end @@ -60,9 +61,9 @@ module Mixlib # value:: The value of the config option # # === Raises - # <ArgumentError>:: If the config option does not exist + # <UnknownConfigOptionError>:: If the config option does not exist and strict mode is on. def [](config_option) - configurable(config_option.to_sym).get(self.configuration) + internal_get(config_option.to_sym) end # Set the value of a config option @@ -73,6 +74,9 @@ module Mixlib # # === Returns # value:: The new value of the config option + # + # === Raises + # <UnknownConfigOptionError>:: If the config option does not exist and strict mode is on. def []=(config_option, value) internal_set(config_option, value) end @@ -100,7 +104,7 @@ module Mixlib # Resets all config options to their defaults. def reset self.configuration = Hash.new - self.contexts.each { |context| context.reset } + self.config_contexts.each { |config_context| config_context.reset } end # Merge an incoming hash with our config options @@ -132,6 +136,7 @@ module Mixlib # metaprogramming to ensure that the slot for method_symbol # gets set to value after any other logic is run + # # === Parameters # method_symbol<Symbol>:: Name of the method (variable setter) # blk<Block>:: logic block to run in setting slot method_symbol to value @@ -142,6 +147,7 @@ module Mixlib end # metaprogramming to set the default value for the given config option + # # === Parameters # symbol<Symbol>:: Name of the config option # default_value<Object>:: Default value (can be unspecified) @@ -190,7 +196,7 @@ module Mixlib # # For example: # - # context :server_info do + # config_context :server_info do # configurable(:url).defaults_to("http://localhost") # end # @@ -198,10 +204,10 @@ module Mixlib # symbol<Symbol>: the name of the context # block<Block>: a block that will be run in the context of this new config # class. - def context(symbol, &block) + def config_context(symbol, &block) context = Class.new context.extend(::Mixlib::Config) - contexts << context + config_contexts << context if block context.instance_eval(&block) end @@ -210,6 +216,53 @@ module Mixlib end end + NOT_PASSED = Object.new + + # Gets or sets strict mode. When strict mode is on, only values which + # were specified with configurable(), default() or writes_with() may be + # retrieved or set. Getting or setting anything else will cause + # Mixlib::Config::UnknownConfigOptionError to be thrown. + # + # If this is set to :warn, unknown values may be get or set, but a warning + # will be printed with Chef::Log.warn if this occurs. + # + # === Parameters + # value<String>:: pass this value to set strict mode [optional] + # + # === Returns + # Current value of config_strict_mode + # + # === Raises + # <ArgumentError>:: if value is set to something other than true, false, or :warn + # + def config_strict_mode(value = NOT_PASSED) + if value == NOT_PASSED + @config_strict_mode || false + else + self.config_strict_mode = value + end + end + + # Sets strict mode. When strict mode is on, only values which + # were specified with configurable(), default() or writes_with() may be + # retrieved or set. All other values + # + # If this is set to :warn, unknown values may be get or set, but a warning + # will be printed with Chef::Log.warn if this occurs. + # + # === Parameters + # value<String>:: pass this value to set strict mode [optional] + # + # === Raises + # <ArgumentError>:: if value is set to something other than true, false, or :warn + # + def config_strict_mode=(value) + if ![ true, false, :warn ].include?(value) + raise ArgumentError, "config_strict_mode must be true, false or :warn" + end + @config_strict_mode = value + end + # Allows for simple lookups and setting of config options via method calls # on Mixlib::Config. If there any arguments to the method, they are used to set # the value of the config option. Otherwise, it's a simple get operation. @@ -220,6 +273,9 @@ module Mixlib # # === Returns # value:: The value of the config option. + # + # === Raises + # <UnknownConfigOptionError>:: If the config option does not exist and strict mode is on. def method_missing(method_symbol, *args) num_args = args.length # Setting @@ -229,7 +285,7 @@ module Mixlib end # Returning - configurable(method_symbol).get(self.configuration) + internal_get(method_symbol) end # Internal dispatch setter, calls the setter (def myvar=) if it is defined, @@ -242,13 +298,38 @@ module Mixlib def internal_set(method_symbol,value) # It would be nice not to have to method_name = method_symbol.id2name + if self.respond_to?("#{method_name}=".to_sym) self.send("#{method_name}=", value) else - configurable(method_symbol).set(self.configuration, value) + if configurables.has_key?(method_symbol) + configurables[method_symbol].set(self.configuration, value) + else + if config_strict_mode == :warn + Chef::Log.warn("Setting unsupported config value #{method_name}..") + elsif self.config_strict_mode + raise UnknownConfigOptionError, "Cannot set unsupported config value #{method_name}." + end + configuration[method_symbol] = value + end end end protected :internal_set + + private + + def internal_get(symbol) + if configurables.has_key?(symbol) + configurables[symbol].get(self.configuration) + else + if config_strict_mode == :warn + Chef::Log.warn("Reading unsupported config value #{symbol}.") + elsif config_strict_mode + raise UnknownConfigOptionError, "Reading unsupported config value #{symbol}." + end + configuration[symbol] + end + end end end diff --git a/lib/mixlib/config/unknown_config_option_error.rb b/lib/mixlib/config/unknown_config_option_error.rb new file mode 100644 index 0000000..83dd1da --- /dev/null +++ b/lib/mixlib/config/unknown_config_option_error.rb @@ -0,0 +1,24 @@ +# +# Author:: John Keiser (<jkeiser@opscode.com>) +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module Mixlib + module Config + class UnknownConfigOptionError < StandardError + end + end +end diff --git a/spec/mixlib/config_spec.rb b/spec/mixlib/config_spec.rb index 4e141c8..ac45b55 100644 --- a/spec/mixlib/config_spec.rb +++ b/spec/mixlib/config_spec.rb @@ -32,9 +32,9 @@ describe Mixlib::Config do end it "should load a config file" do - File.stub!(:exists?).and_return(true) - File.stub!(:readable?).and_return(true) - IO.stub!(:read).with('config.rb').and_return("alpha = 'omega'\nfoo = 'bar'") + File.stub(:exists?).and_return(true) + File.stub(:readable?).and_return(true) + IO.stub(:read).with('config.rb').and_return("alpha = 'omega'\nfoo = 'bar'") lambda { ConfigIt.from_file('config.rb') }.should_not raise_error @@ -43,7 +43,7 @@ describe Mixlib::Config do it "should not raise an ArgumentError with an explanation if you try and set a non-existent variable" do lambda { ConfigIt[:foobar] = "blah" - }.should_not raise_error(ArgumentError) + }.should_not raise_error end it "should raise an Errno::ENOENT if it can't find the file" do @@ -53,7 +53,7 @@ describe Mixlib::Config do end it "should allow the error to bubble up when it's anything other than IOError" do - IO.stub!(:read).with('config.rb').and_return("@#asdf") + IO.stub(:read).with('config.rb').and_return("@#asdf") lambda { ConfigIt.from_file('config.rb') }.should raise_error(SyntaxError) @@ -80,6 +80,39 @@ describe Mixlib::Config do ConfigIt[:arbitrary_value].should == 50 end + describe "when strict mode is on" do + class StrictClass + extend ::Mixlib::Config + config_strict_mode true + default :x, 1 + end + + it "allows you to get and set configured values" do + StrictClass.x = StrictClass.x * 2 + StrictClass[:x] = StrictClass[:x] * 2 + end + + it "raises an error when you get an arbitrary config option with .y" do + lambda { StrictClass.y }.should raise_error(Mixlib::Config::UnknownConfigOptionError) + end + + it "raises an error when you get an arbitrary config option with [:y]" do + lambda { StrictClass[:y] }.should raise_error(Mixlib::Config::UnknownConfigOptionError) + end + + it "raises an error when you set an arbitrary config option with .y = 10" do + lambda { StrictClass.y = 10 }.should raise_error(Mixlib::Config::UnknownConfigOptionError) + end + + it "raises an error when you get an arbitrary config option with .y 10" do + lambda { StrictClass.y 10 }.should raise_error(Mixlib::Config::UnknownConfigOptionError) + end + + it "raises an error when you get an arbitrary config option with [:y] = 10" do + lambda { StrictClass[:y] = 10 }.should raise_error(Mixlib::Config::UnknownConfigOptionError) + end + end + describe "when a block has been used to set config values" do before do ConfigIt.configure { |c| c[:cookbook_path] = "monkey_rabbit"; c[:otherthing] = "boo" } @@ -96,7 +129,7 @@ describe Mixlib::Config do end it "should not raise an ArgumentError if you access a config option that does not exist" do - lambda { ConfigIt[:snob_hobbery] }.should_not raise_error(ArgumentError) + lambda { ConfigIt[:snob_hobbery] }.should_not raise_error end it "should return true or false with has_key?" do @@ -132,7 +165,7 @@ describe Mixlib::Config do end it "should multiply an integer by 1000 via from-file, too" do - IO.stub!(:read).with('config.rb').and_return("test_method 99") + IO.stub(:read).with('config.rb').and_return("test_method 99") @klass.from_file('config.rb') @klass.test_method.should == 99000 end @@ -317,7 +350,7 @@ describe Mixlib::Config do @klass = Class.new @klass.extend(::Mixlib::Config) @klass.class_eval do - context(:blah) do + config_context(:blah) do default :x, 5 end end @@ -351,8 +384,8 @@ describe Mixlib::Config do @klass = Class.new @klass.extend(::Mixlib::Config) @klass.class_eval do - context(:blah) do - context(:yarr) do + config_context(:blah) do + config_context(:yarr) do default :x, 5 end end @@ -381,4 +414,39 @@ describe Mixlib::Config do @klass.blah.yarr.x.should == 5 end end + + describe "When a nested context has strict mode on" do + class StrictClass2 + extend ::Mixlib::Config + config_context :c do + config_strict_mode true + default :x, 1 + end + end + + it "The parent class allows you to set arbitrary config options" do + StrictClass2.y = 10 + end + + it "The nested class does not allow you to set arbitrary config options" do + lambda { StrictClass2.c.y = 10 }.should raise_error(Mixlib::Config::UnknownConfigOptionError) + end + end + + describe "When strict mode is on but a nested context has strict mode unspecified" do + class StrictClass3 + extend ::Mixlib::Config + config_strict_mode true + default :x, 1 + config_context :c + end + + it "The parent class does not allow you to set arbitrary config options" do + lambda { StrictClass3.y = 10 }.should raise_error(Mixlib::Config::UnknownConfigOptionError) + end + + it "The nested class allows you to set arbitrary config options" do + StrictClass3.c.y = 10 + end + end end |