summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <jkeiser@opscode.com>2013-09-11 08:23:10 -0700
committerJohn Keiser <jkeiser@opscode.com>2013-09-11 08:23:10 -0700
commit9ee0d266b3292c7b1085c8f22cffbd298cf1e7ce (patch)
tree09e3573fcef0c10b0d4bc8ed052c987180349651
parent191e22c743c4d6127c0aef0c6af8f412b3e105aa (diff)
parent41ea64371a277894a2ecafe3f8b77780edb4ea2a (diff)
downloadmixlib-config-9ee0d266b3292c7b1085c8f22cffbd298cf1e7ce.tar.gz
Add strict mode
-rw-r--r--lib/mixlib/config.rb105
-rw-r--r--lib/mixlib/config/unknown_config_option_error.rb24
-rw-r--r--spec/mixlib/config_spec.rb88
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