diff options
author | Adam Jacob <adam@hjksolutions.com> | 2008-04-07 18:14:25 -0700 |
---|---|---|
committer | Adam Jacob <adam@hjksolutions.com> | 2008-04-07 18:14:25 -0700 |
commit | 434f25ba07b5c0c50baa1e15b14a945bba3c3c3b (patch) | |
tree | 03085c689549a62d546d87e6484dde8756da2d23 /lib | |
parent | f543b509ba61dd347512e8a9e3153a49a2a8cb6b (diff) | |
download | chef-434f25ba07b5c0c50baa1e15b14a945bba3c3c3b.tar.gz |
Adding the Params::Validate mixin, refactored Chef::Config to be a singleton, Implemented require_recipe
Diffstat (limited to 'lib')
-rw-r--r-- | lib/chef/config.rb | 78 | ||||
-rw-r--r-- | lib/chef/cookbook.rb | 13 | ||||
-rw-r--r-- | lib/chef/cookbook_loader.rb | 7 | ||||
-rw-r--r-- | lib/chef/mixin/from_file.rb | 12 | ||||
-rw-r--r-- | lib/chef/mixin/params_validate.rb | 162 | ||||
-rw-r--r-- | lib/chef/node.rb | 21 | ||||
-rw-r--r-- | lib/chef/recipe.rb | 49 | ||||
-rw-r--r-- | lib/chef/resource.rb | 9 | ||||
-rw-r--r-- | lib/chef/resource/file.rb | 4 |
9 files changed, 283 insertions, 72 deletions
diff --git a/lib/chef/config.rb b/lib/chef/config.rb index 6c0402bdda..191de2504c 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -19,51 +19,59 @@ # require File.join(File.dirname(__FILE__), "mixin", "check_helper") +require File.join(File.dirname(__FILE__), "mixin", "from_file") + +# Chef::Config[:variable] +# @config = Chef::Config.new() +# +# Chef::ConfigFast << Chef::Config +# +# Chef::Config.from_file(foo) +# Chef::Resource.from_file (NoMethodError) +# Chef::Config[:cookbook_path] +# Chef::Config.cookbook_path +# Chef::Config.cookbook_path "one", "two" class Chef class Config - include Chef::Mixin::CheckHelper + + @configuration = { + :cookbook_path => [ "/etc/chef/site-cookbook", "/etc/chef/cookbook" ] + } - def initialize - set_defaults - end - - def self.load_file(file) - config = Chef::Config.new - if File.exists?(file) && File.readable?(file) - begin - config.instance_eval(IO.read(file), file, 1) - rescue NoMethodError => e - new_message = "You probably tried to use a config variable that doesn't exist!\n" - new_message += e.message - raise e.exception(new_message) + class << self + include Chef::Mixin::FromFile + + def configure(&block) + yield @configuration + end + + def [](config_option) + if @configuration.has_key?(config_option.to_sym) + @configuration[config_option.to_sym] + else + raise ArgumentError, "Cannot find configuration option #{config_option.to_s}" end - else - raise IOError, "Cannot find or read #{file}!" end - config - end + + def []=(config_option, value) + @configuration[config_option.to_sym] = value + end - def cookbook_path(*args) - if args.length == 0 - @cookbook_path - else - flat_args = args.flatten - flat_args.each do |a| - unless a.kind_of?(String) - raise ArgumentError, "You must pass strings to cookbook_path!" + def method_missing(method_symbol, *args) + if @configuration.has_key?(method_symbol) + if args.length == 1 + @configuration[method_symbol] = args[0] + elsif args.length > 1 + @configuration[method_symbol] = args end + return @configuration[method_symbol] + else + raise ArgumentError, "Cannot find configuration option #{method_symbol.to_s}" end - @cookbook_path = flat_args end - end - - def set_defaults - @cookbook_path = [ - "/etc/chef/site-cookbook", - "/etc/chef/cookbook", - ] - end + + end # class << self end end
\ No newline at end of file diff --git a/lib/chef/cookbook.rb b/lib/chef/cookbook.rb index c69491c2f2..218ecb3c10 100644 --- a/lib/chef/cookbook.rb +++ b/lib/chef/cookbook.rb @@ -86,20 +86,17 @@ class Chef results end - def load_recipe(name, node, collection=nil, definitions=nil, config=nil) + def load_recipe(name, node, collection=nil, definitions=nil, cookbook_loader=nil) cookbook_name = @name recipe_name = nil - case name - when /^(.+)::(.+)$/ - recipe_name = $2 - else - recipe_name = name - end + nmatch = name.match(/^(.+)::(.+)$/) + recipe_name = nmatch ? nmatch[2] : name + unless @recipe_names.has_key?(recipe_name) raise ArgumentError, "Cannot find a recipe matching #{recipe_name} in cookbook #{@name}" end recipe = Chef::Recipe.new(cookbook_name, recipe_name, node, - collection, definitions, config) + collection, definitions, cookbook_loader) recipe.from_file(@recipe_files[@recipe_names[recipe_name]]) recipe end diff --git a/lib/chef/cookbook_loader.rb b/lib/chef/cookbook_loader.rb index d585b1e440..24ec01e574 100644 --- a/lib/chef/cookbook_loader.rb +++ b/lib/chef/cookbook_loader.rb @@ -21,19 +21,18 @@ class Chef class CookbookLoader - attr_accessor :cookbook, :config + attr_accessor :cookbook include Enumerable - def initialize(config) - @config = config + def initialize() @cookbook = Hash.new load_cookbooks end def load_cookbooks cookbook_settings = Hash.new - @config.cookbook_path.each do |cb_path| + Chef::Config.cookbook_path.each do |cb_path| Dir[File.join(cb_path, "*")].each do |cookbook| next unless File.directory?(cookbook) cookbook_name = File.basename(cookbook).to_sym diff --git a/lib/chef/mixin/from_file.rb b/lib/chef/mixin/from_file.rb index 68f614c07a..b1ad600ef8 100644 --- a/lib/chef/mixin/from_file.rb +++ b/lib/chef/mixin/from_file.rb @@ -1,3 +1,8 @@ +# +# Chef::Mixin::FromFile +# +# A mixin that adds instance_eval support to a given object. +# # Author:: Adam Jacob (<adam@hjksolutions.com>) # Copyright:: Copyright (c) 2008 HJK Solutions, LLC # License:: GNU General Public License version 2 or later @@ -19,7 +24,12 @@ class Chef module Mixin - module FromFile + module FromFile + + # Loads a given ruby file, and runs instance_eval against it in the context of the current + # object. + # + # Raises an IOError if the file cannot be found, or is not readable. def from_file(filename) if File.exists?(filename) && File.readable?(filename) self.instance_eval(IO.read(filename), filename, 1) diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb new file mode 100644 index 0000000000..4c6b5f6612 --- /dev/null +++ b/lib/chef/mixin/params_validate.rb @@ -0,0 +1,162 @@ +# +# Chef::Mixin::ParamsValidate +# +# Because I can't deal with not having named params. Strongly based on Dave Rolsky's excellent +# Params::Validate module for Perl. Please don't blame him, though, if you hate this. :) +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: GNU General Public License version 2 or later +# +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +class Chef + module Mixin + module ParamsValidate + + # Takes a hash of options, along with a map to validate them. Returns the original + # options hash, plus any changes that might have been made (through things like setting + # default values in the validation map) + # + # For example: + # + # validate({ :one => "neat" }, { :one => { :kind_of => String }}) + # + # Would raise an exception if the value of :one above is not a kind_of? string. Valid + # map options are: + # + # :default:: Sets the default value for this parameter. + # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid. + # The key will be inserted into the error message if the Proc does not return true: + # "Option #{key}'s value #{value} #{message}!" + # :kind_of:: Ensure that the value is a kind_of?(Whatever) + # :respond_to:: Esnure that the value has a given method. Takes one method name or an array of + # method names. + # :required:: Raise an exception if this parameter is missing. Valid values are true or false, + # by default, options are not required. + # :regex:: Match the value of the paramater against a regular expression. + def validate(opts, map) + #-- + # validate works by taking the keys in the validation map, assuming it's a hash, and + # looking for _pv_:symbol as methods. Assuming it find them, it calls the right + # one. + #++ + raise ArgumentError, "Options must be a hash" unless opts.kind_of?(Hash) + raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash) + + map.each do |key, validation| + unless key.kind_of?(Symbol) || key.kind_of?(String) + raise ArgumentError, "Validation map keys must be symbols or strings!" + end + case validation + when true + _pv_required(opts, key) + when false + true + when Hash + validation.each do |check, carg| + check_method = "_pv_#{check.to_s}" + if self.respond_to?(check_method, true) + self.send(check_method, opts, key, carg) + else + raise ArgumentError, "Validation map has unknown check: #{check}" + end + end + end + end + opts + end + + private + + # Return the value of a parameter, or nil if it doesn't exist. + def _pv_opts_lookup(opts, key) + if opts.has_key?(key.to_s) + opts[key.to_s] + elsif opts.has_key?(key.to_sym) + opts[key.to_sym] + else + nil + end + end + + # Raise an exception if the parameter is not found. + def _pv_required(opts, key, is_required=true) + if is_required + if opts.has_key?(key.to_s) || opts.has_key?(key.to_sym) + true + else + raise ArgumentError, "Required argument #{key} is missing!" + end + end + end + + # Raise an exception if the parameter is not a kind_of?(to_be) + def _pv_kind_of(opts, key, to_be) + value = _pv_opts_lookup(opts, key) + if value != nil + unless value.kind_of?(to_be) + raise ArgumentError, "Option #{key} must be a kind of #{to_be}! You passed #{to_be.inspect}." + end + end + end + + # Raise an exception if the parameter does not respond to a given set of methods. + def _pv_respond_to(opts, key, method_name_list) + value = _pv_opts_lookup(opts, key) + if value != nil + method_name_list.to_a.each do |method_name| + unless value.respond_to?(method_name) + raise ArgumentError, "Option #{key} must have a #{method_name} method!" + end + end + end + end + + # Assign a default value to a parameter. + def _pv_default(opts, key, default_value) + value = _pv_opts_lookup(opts, key) + if value == nil + opts[key] = default_value + end + end + + # Check a parameter against a regular expression. + def _pv_regex(opts, key, regex) + value = _pv_opts_lookup(opts, key) + if value != nil + if regex.match(value) == nil + raise ArgumentError, "Option #{key}'s value #{value} does not match regular expression #{regex.to_s}" + end + end + end + + # Check a parameter against a hash of proc's. + def _pv_callbacks(opts, key, callbacks) + raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash) + value = _pv_opts_lookup(opts, key) + if value != nil + callbacks.each do |message, zeproc| + if zeproc.call(value) != true + raise ArgumentError, "Option #{key}'s value #{value} #{message}!" + end + end + end + end + end + end +end + diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 69dc6ab3f0..d5896fbd95 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -1,4 +1,7 @@ # +# Chef::Node +# +# # Author:: Adam Jacob (<adam@hjksolutions.com>) # Copyright:: Copyright (c) 2008 HJK Solutions, LLC # License:: GNU General Public License version 2 or later @@ -29,12 +32,14 @@ class Chef include Chef::Mixin::CheckHelper include Chef::Mixin::FromFile + # Create a new Chef::Node object. def initialize() @name = nil @attribute = Hash.new @recipe_list = Array.new end + # Set the name of this Node, or return the current name. def name(arg=nil) set_if_args(@name, arg) do |a| case a @@ -46,6 +51,7 @@ class Chef end end + # Return an attribute of this node. Returns nil if the attribute is not found. def [](attrib) if @attribute.has_key?(attrib) @attribute[attrib] @@ -56,6 +62,15 @@ class Chef end end + # Iterates over each attribute, passing the attribute and value to the block. + def each_attribute(&block) + @attribute.each do |k,v| + yield(k, v) + end + end + + # Return true if this Node has a given attribute, false if not. Takes either a symbol or + # a string. def attribute?(attrib) result = false result = @attribute.has_key?(attrib) @@ -63,10 +78,13 @@ class Chef return @attribute.has_key?(attrib.to_sym) end + # Returns true if this Node expects a given recipe, false if not. def recipe?(recipe_name) @recipe_list.detect { |r| r == recipe_name } ? true : false end + # Returns an Array of recipes. If you call it with arguments, they will become the new + # list of recipes. def recipes(*args) if args.length > 0 @recipe_list = args.flatten @@ -75,6 +93,9 @@ class Chef end end + # Set an attribute based on the missing method. If you pass an argument, we'll use that + # to set the attribute values. Otherwise, we'll wind up just returning the attributes + # value. def method_missing(symbol, *args) if args.length != 0 @attribute[symbol] = args.length == 1 ? args[0] : args diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb index 81fb979de2..af1cbd2d77 100644 --- a/lib/chef/recipe.rb +++ b/lib/chef/recipe.rb @@ -26,30 +26,47 @@ class Chef include Chef::Mixin::FromFile attr_accessor :cookbook_name, :recipe_name, :recipe, :node, :collection, - :definitions, :config, :params + :definitions, :params, :cookbook_loader - def initialize(cookbook_name, recipe_name, node, collection=nil, definitions=nil, config=nil) + def initialize(cookbook_name, recipe_name, node, collection=nil, definitions=nil, cookbook_loader=nil) @cookbook_name = cookbook_name @recipe_name = recipe_name @node = node + if collection @collection = collection else @collection = Chef::ResourceCollection.new() end - if config - @config = config - else - @config = Chef::Config.new() - end + if definitions @definitions = definitions else @definitions = Hash.new end + + if cookbook_loader + @cookbook_loader = cookbook_loader + else + @cookbook_loader = Chef::CookbookLoader.new() + end + @params = Hash.new end + def require_recipe(*args) + args.flatten.each do |recipe| + rmatch = recipe.match(/(.+?)::(.+)/) + if rmatch + cookbook = @cookbook_loader[rmatch[1]] + cookbook.load_recipe(rmatch[2], @node, @collection, @definitions, @cookbook_loader) + else + cookbook = @cookbook_loader[recipe] + cookbook.load_recipe("default", @node, @collection, @definitions, @cookbook_loader) + end + end + end + def resources(*args) @collection.resources(*args) end @@ -58,26 +75,28 @@ class Chef resource = nil # If we have a definition that matches, we want to use that instead. This should # let you do some really crazy over-riding of "native" types, if you really want - # to. + # to. if @definitions.has_key?(method_symbol) new_def = @definitions[method_symbol].dup new_def.instance_eval(&block) - new_recipe = Chef::Recipe.new(@cookbook_name, @recipe_name, @node, @collection, @definitions, @config) + new_recipe = Chef::Recipe.new(@cookbook_name, @recipe_name, @node, @collection, @definitions, @cookbook_loader) new_recipe.params = new_def.params new_recipe.instance_eval(&new_def.recipe) else method_name = method_symbol.to_s # Otherwise, we're rocking the regular resource call route. rname = nil - case method_name - when /^(.+)_(.+)$/ - rname = "Chef::Resource::#{$1.capitalize}#{$2.capitalize}" - when /^(.+)$/ - rname = "Chef::Resource::#{$1.capitalize}" + mn = method_name.match(/^(.+)_(.+)$/) + if mn + rname = "Chef::Resource::#{mn[1].capitalize}#{mn[2].capitalize}" + else + short_match = method_name.match(/^(.+)$/) + if short_match + rname = "Chef::Resource::#{short_match[1].capitalize}" + end end begin args << @collection - args << @config resource = eval(rname).new(*args) resource.params = @params resource.instance_eval(&block) diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 2d7b90ed7f..fab6279a0e 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -26,21 +26,16 @@ class Chef include Chef::Mixin::CheckHelper - attr_accessor :tag, :actions, :config, :params + attr_accessor :tag, :actions, :params attr_reader :name, :noop, :resource_name, :collection, :notifies, :subscribes - def initialize(name, collection=nil, config=nil) + def initialize(name, collection=nil) @name = name if collection @collection = collection else @collection = Chef::ResourceCollection.new() end - if config - @config = config - else - @config = Chef::Config.new() - end @tag = [ name.to_s ] @noop = nil @before = nil diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb index 3132aa5a1a..59c51ba8d3 100644 --- a/lib/chef/resource/file.rb +++ b/lib/chef/resource/file.rb @@ -22,9 +22,9 @@ class Chef class Resource class File < Chef::Resource - def initialize(name, collection=nil, config=nil) + def initialize(name, collection=nil) @resource_name = :file - super(name, collection, config) + super(name, collection) @path = name @backup = true @action = "create" |