diff options
32 files changed, 1178 insertions, 129 deletions
diff --git a/History.txt b/History.txt index d7b1defce5..af81c7d967 100644 --- a/History.txt +++ b/History.txt @@ -3,3 +3,5 @@ * 1 major enhancement * Birthday! +Sun Apr 27 20:30:43 PDT 2008 + * Compiled first full recipes, and actually passed them to a provider. :)
\ No newline at end of file diff --git a/Manifest.txt b/Manifest.txt index 203b0f1f63..38793f098b 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -4,9 +4,6 @@ Manifest.txt README.txt Rakefile bin/chef-solo -docs/chef-head.txt -docs/design/HighLevel.graffle -docs/recipe.rb examples/config.rb examples/config/chef-solo.rb examples/config/cookbooks/fakefile/recipes/default.rb diff --git a/bin/chef-solo b/bin/chef-solo index 0d49414f5a..747c672b32 100755 --- a/bin/chef-solo +++ b/bin/chef-solo @@ -60,6 +60,9 @@ end # Load our config file Chef::Config.from_file(config[:config_file]) +if config[:log_level] + Chef::Log.level(config[:log_level].to_sym) +end # Find out our own hostname. node_name = Facter["fqdn"].value @@ -73,13 +76,7 @@ compile.load_node(node_name) Facter.each do |field, value| compile.node[field.to_sym] = value end - -puts compile.node.to_yaml - compile.load_definitions -puts compile.definitions.to_yaml - compile.load_recipes -puts compile.resource_collection.to_yaml - -puts compile.to_yaml +cr = Chef::Runner.new(compile.node, compile.collection) +cr.converge
\ No newline at end of file diff --git a/examples/config/chef-solo.rb b/examples/config/chef-solo.rb index 7270277214..6e98fbbc22 100644 --- a/examples/config/chef-solo.rb +++ b/examples/config/chef-solo.rb @@ -3,4 +3,6 @@ cookbook_path File.join(File.dirname(__FILE__), "cookbooks") node_path File.join(File.dirname(__FILE__), "nodes") -log_level :debug +log_level :info + +Chef::Log::Formatter.show_time = false diff --git a/examples/config/cookbooks/fakefile/recipes/default.rb b/examples/config/cookbooks/fakefile/recipes/default.rb index 2f91563bfe..35d8bb828d 100644 --- a/examples/config/cookbooks/fakefile/recipes/default.rb +++ b/examples/config/cookbooks/fakefile/recipes/default.rb @@ -1,5 +1,6 @@ -file "foo" do +file "/tmp/foo" do owner "adam" - mode 644 - action "create" + mode 0644 + action :create + notifies :delete, resources(:file => "/tmp/glen"), :delayed end diff --git a/examples/config/cookbooks/tempfile/recipes/default.rb b/examples/config/cookbooks/tempfile/recipes/default.rb index f0888ca550..7b0c3e278a 100644 --- a/examples/config/cookbooks/tempfile/recipes/default.rb +++ b/examples/config/cookbooks/tempfile/recipes/default.rb @@ -1,5 +1,5 @@ -file "glen" do - owner "glen" - mode 644 +file "/tmp/glen" do + owner "adam" + mode 0755 action "create" end diff --git a/examples/sample_recipe.rb b/examples/sample_recipe.rb index 234a1587a4..4b8548cb90 100644 --- a/examples/sample_recipe.rb +++ b/examples/sample_recipe.rb @@ -16,6 +16,11 @@ file "/etc/nsswitch.conf" do notifies :restart, resources("service[openldap]"), :immediately end +service "apache2" do + action "enabled" + subscribes :restart, resources("/etc/nsswitch.conf"), :immediately +end + file "/etc/ldap.conf" do owner "root" group "root" diff --git a/lib/chef/compile.rb b/lib/chef/compile.rb index 5b1f9d756e..e974852426 100644 --- a/lib/chef/compile.rb +++ b/lib/chef/compile.rb @@ -25,12 +25,12 @@ class Chef class Compile - attr_accessor :node, :cookbook_loader, :resource_collection, :definitions + attr_accessor :node, :cookbook_loader, :collection, :definitions def initialize() @node = nil @cookbook_loader = Chef::CookbookLoader.new - @resource_collection = Chef::ResourceCollection.new + @collection = Chef::ResourceCollection.new @definitions = Hash.new end @@ -50,10 +50,10 @@ class Chef rmatch = recipe.match(/(.+?)::(.+)/) if rmatch cookbook = @cookbook_loader[rmatch[1]] - cookbook.load_recipe(rmatch[2], @node, @resource_collection, @definitions, @cookbook_loader) + cookbook.load_recipe(rmatch[2], @node, @collection, @definitions, @cookbook_loader) else cookbook = @cookbook_loader[recipe] - cookbook.load_recipe("default", @node, @resource_collection, @definitions, @cookbook_loader) + cookbook.load_recipe("default", @node, @collection, @definitions, @cookbook_loader) end end end diff --git a/lib/chef/log/formatter.rb b/lib/chef/log/formatter.rb index d177060d97..1c7044bdb1 100644 --- a/lib/chef/log/formatter.rb +++ b/lib/chef/log/formatter.rb @@ -28,9 +28,20 @@ require 'time' class Chef class Log class Formatter < Logger::Formatter - # Prints a log message as '[time] severity: message' + @@show_time = true + + def self.show_time=(show=false) + @@show_time = show + end + + # Prints a log message as '[time] severity: message' if Chef::Log::Formatter.show_time == true. + # Otherwise, doesn't print the time. def call(severity, time, progname, msg) - sprintf("[%s] %s: %s\n", time.rfc2822(), severity, msg2str(msg)) + if @@show_time + sprintf("[%s] %s: %s\n", time.rfc2822(), severity, msg2str(msg)) + else + sprintf("%s: %s\n", severity, msg2str(msg)) + end end # Converts some argument to a Logger.severity() call to a string. Regular strings pass through like diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb index 4c6b5f6612..29c0b6d002 100644 --- a/lib/chef/mixin/params_validate.rb +++ b/lib/chef/mixin/params_validate.rb @@ -2,7 +2,7 @@ # 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. :) +# Params::Validate module for Perl. Please don't blame him, though, this is full of bugs. :) # # Author:: Adam Jacob (<adam@hjksolutions.com>) # Copyright:: Copyright (c) 2008 HJK Solutions, LLC @@ -42,12 +42,15 @@ class Chef # :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 + # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure + # that the value is one of those types. + # :respond_to:: Ensure 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. + # :equal_to:: Match the value of the paramater with ==. An array means it can be equal to any + # of the values. def validate(opts, map) #-- # validate works by taking the keys in the validation map, assuming it's a hash, and @@ -80,8 +83,19 @@ class Chef opts end + def set_or_return(instance, new_value, opts, map) + if new_value != nil + validate(opts, map) + instance = new_value + else + instance + end + 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) @@ -104,12 +118,33 @@ class Chef end end + def _pv_equal_to(opts, key, to_be) + value = _pv_opts_lookup(opts, key) + if value != nil + passes = false + [ to_be ].flatten.each do |tb| + if value == tb + passes = true + end + end + unless passes + raise ArgumentError, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}." + 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}." + passes = false + [ to_be ].flatten.each do |tb| + if value.kind_of?(tb) + passes = true + end + end + unless passes + raise ArgumentError, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}." end end end @@ -137,11 +172,17 @@ class Chef # 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}" + passes = false + [ regex ].flatten.each do |r| + if value != nil + if r.match(value.to_s) + passes = true + end end end + unless passes + raise ArgumentError, "Option #{key}'s value #{value} does not match regular expression #{regex.to_s}" + end end # Check a parameter against a hash of proc's. diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 7cb42effd0..c5be016ec8 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -21,6 +21,7 @@ # require File.join(File.dirname(__FILE__), "mixin", "check_helper") +require File.join(File.dirname(__FILE__), "mixin", "params_validate") require File.join(File.dirname(__FILE__), "mixin", "from_file") require 'rubygems' @@ -33,6 +34,7 @@ class Chef include Chef::Mixin::CheckHelper include Chef::Mixin::FromFile + include Chef::Mixin::ParamsValidate # Create a new Chef::Node object. def initialize() @@ -79,13 +81,18 @@ class Chef # Set the name of this Node, or return the current name. def name(arg=nil) - set_if_args(@name, arg) do |a| - case a - when String - @name = a - else - raise ArgumentError, "The nodes name must be a string" - end + if arg != nil + validate( + { :name => arg }, + { + :name => { + :kind_of => String + } + } + ) + @name = arg + else + @name end end @@ -168,5 +175,10 @@ class Chef result_object.to_json end + # As a string + def to_s + "node[#{@name}]" + end + end end
\ No newline at end of file diff --git a/lib/chef/platform.rb b/lib/chef/platform.rb new file mode 100644 index 0000000000..bebdcfa297 --- /dev/null +++ b/lib/chef/platform.rb @@ -0,0 +1,172 @@ +# +# 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 +# + +Dir[File.join(File.dirname(__FILE__), 'provider/**/*.rb')].sort.each { |lib| require lib } +require File.join(File.dirname(__FILE__), 'mixin', 'params_validate') + +class Chef + class Platform + + @platforms = { + :mac_os_x => {}, + :ubuntu => {}, + :centos => {}, + :redhat => {}, + :gentoo => {}, + :solaris => {}, + :default => { + :file => Chef::Provider::File + } + } + + class << self + attr_accessor :platforms + + include Chef::Mixin::ParamsValidate + + def find(name, version) + provider_map = @platforms[:default].clone + + name_sym = name + if name.kind_of?(String) + name.downcase! + name.gsub!(/\s/, "_") + name_sym = name.to_sym + end + + if @platforms.has_key?(name_sym) + if @platforms[name_sym].has_key?(version) + Chef::Log.debug("Platform #{name.to_s} version #{version} found") + if @platforms[name_sym].has_key?(:default) + provider_map.merge!(@platforms[name_sym][:default]) + end + provider_map.merge!(@platforms[name_sym][version]) + elsif @platforms[name_sym].has_key?(:default) + provider_map.merge!(@platforms[name_sym][:default]) + end + else + Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)") + end + provider_map + end + + def find_provider(platform, version, resource_type) + pmap = Chef::Platform.find(platform, version) + rtkey = resource_type + if resource_type.kind_of?(Chef::Resource) + rtkey = resource_type.resource_name + end + if pmap.has_key?(rtkey) + pmap[rtkey] + else + raise( + ArgumentError, + "Cannot find a provider for #{resource_type} on #{platform} version #{version}" + ) + end + end + + def find_provider_for_node(node, resource_type) + platform = nil + version = nil + if node.attribute?("lsbdistid") + platform = node[:lsbdistid] + elsif node.attribute?("macosx_productname") + platform = node[:macosx_productname] + elsif node.attribute?("operatingsystem") + platform = node[:operatingsystem] + end + raise ArgumentError, "Cannot find a platform for #{node}" unless platform + + if node.attribute?("lsbdistrelease") + version = node[:lsbdistrelease] + elsif node.attribute?("macosx_productversion") + version = node[:macosx_productversion] + elsif node.attribute?("operatingsystemversion") + version = node[:operatingsystemversion] + end + raise ArgumentError, "Cannot find a version for #{node}" unless version + + provider = find_provider(platform, version, resource_type) + end + + def set(args) + validate( + args, + { + :platform => { + :kind_of => Symbol, + :required => false, + }, + :version => { + :kind_of => String, + :required => false, + }, + :resource => { + :kind_of => Symbol, + }, + :provider => { + :kind_of => [ String, Symbol, Class ], + } + } + ) + if args.has_key?(:platform) + if args.has_key?(:version) + if @platforms.has_key?(args[:platform]) + if @platforms[args[:platform]].has_key?(args[:version]) + @platforms[args[:platform]][args[:version]][args[:resource].to_sym] = args[:provider] + else + @platforms[args[:platform]][args[:version]] = { + args[:resource].to_sym => args[:provider] + } + end + else + @platforms[args[:platform]] = { + args[:version] => { + args[:resource].to_sym => args[:provider] + } + } + end + else + if @platforms.has_key?(args[:platform]) + @platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider] + else + @platforms[args[:platform]] = { + :default => { + args[:resource].to_sym => args[:provider] + } + } + end + end + else + if @platforms.has_key?(:default) + @platforms[:default][args[:resource].to_sym] = args[:provider] + else + @platforms[:default] = { + args[:resource].to_sym => args[:provider] + } + end + end + end + + end + + end +end
\ No newline at end of file diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index d296b63227..018f687069 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -29,5 +29,10 @@ class Chef @current_resource = nil end + def action_nothing + Chef::Log.debug("Doing nothing for #{self.to_s}") + true + end + end end
\ No newline at end of file diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb index c0805674ec..7bb729cd87 100644 --- a/lib/chef/provider/file.rb +++ b/lib/chef/provider/file.rb @@ -47,58 +47,100 @@ class Chef # Compare the ownership of a file. Returns true if they are the same, false if they are not. def compare_owner - case @new_resource.owner - when /^\d+$/, Integer - @set_user_id = @new_resource.owner.to_i - @set_user_id == @current_resource.owner - else - # This raises an ArugmentError if you can't find the user - user_info = Etc.getpwnam(@new_resource.owner) - @set_user_id = user_info.uid - @set_user_id == @current_resource.owner + if @new_resource.owner != nil + case @new_resource.owner + when /^\d+$/, Integer + @set_user_id = @new_resource.owner.to_i + @set_user_id == @current_resource.owner + else + # This raises an ArugmentError if you can't find the user + user_info = Etc.getpwnam(@new_resource.owner) + @set_user_id = user_info.uid + @set_user_id == @current_resource.owner + end end end # Set the ownership on the file, assuming it is not set correctly already. def set_owner unless compare_owner + Chef::Log.info("Setting owner to #{@set_user_id} for #{@new_resource}") ::File.chown(@set_user_id, nil, @new_resource.path) + @new_resource.updated = true end end # Compares the group of a file. Returns true if they are the same, false if they are not. def compare_group - case @new_resource.group - when /^\d+$/, Integer - @set_group_id = @new_resource.group.to_i - @set_group_id == @current_resource.group - else - group_info = Etc.getgrnam(@new_resource.group) - @set_group_id = group_info.gid - @set_group_id == @current_resource.group + if @new_resource.group != nil + case @new_resource.group + when /^\d+$/, Integer + @set_group_id = @new_resource.group.to_i + @set_group_id == @current_resource.group + else + group_info = Etc.getgrnam(@new_resource.group) + @set_group_id = group_info.gid + @set_group_id == @current_resource.group + end end end def set_group unless compare_group + Chef::Log.info("Setting group to #{@set_group_id} for #{@new_resource}") ::File.chown(nil, @set_group_id, @new_resource.path) + @new_resource.updated = true + end + end + + def compare_mode + if @new_resource.mode != nil + case @new_resource.mode + when /^\d+$/, Integer + real_mode = sprintf("%o" % (@new_resource.mode & 007777)) + real_mode.to_i == @current_resource.mode.to_i + end + end + end + + def set_mode + unless compare_mode && @new_resource.mode != nil + Chef::Log.info("Setting mode to #{sprintf("%o" % (@new_resource.mode & 007777)) + } for #{@new_resource}") + ::File.chmod(@new_resource.mode.to_i, @new_resource.path) + @new_resource.updated = true end end def action_create unless ::File.exists?(@new_resource.path) + Chef::Log.info("Creating #{@new_resource} at #{@new_resource.path}") ::File.open(@new_resource.path, "w+") { |f| } + @new_resource.updated = true end - set_owner - set_group + set_owner if @new_resource.owner != nil + set_group if @new_resource.group != nil + set_mode if @new_resource.mode != nil end def action_delete if ::File.exists?(@new_resource.path) && ::File.writable?(@new_resource.path) + Chef::Log.info("Deleting #{@new_resource} at #{@new_resource.path}") ::File.delete(@new_resource.path) + @new_resource.updated = true + else + raise "Cannot delete #{@new_resource} at #{@new_resource_path}!" end end + def action_touch + action_create + time = Time.now + Chef::Log.info("Updating #{@new_resource} with new atime/mtime of #{time}") + ::File.utime(time, time, @new_resource.path) + @new_resource.updated = true + end + end end end
\ No newline at end of file diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index fab6279a0e..5a5263b321 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -18,16 +18,18 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # +require File.join(File.dirname(__FILE__), "mixin", "params_validate") require File.join(File.dirname(__FILE__), "mixin", "check_helper") require 'yaml' class Chef class Resource - + include Chef::Mixin::CheckHelper + include Chef::Mixin::ParamsValidate - attr_accessor :tag, :actions, :params - attr_reader :name, :noop, :resource_name, :collection, :notifies, :subscribes + attr_accessor :tag, :actions, :params, :provider, :updated, :allowed_actions + attr_reader :resource_name, :collection def initialize(name, collection=nil) @name = name @@ -41,6 +43,28 @@ class Chef @before = nil @actions = Hash.new @params = Hash.new + @provider = nil + @allowed_actions = [ :nothing ] + @action = :nothing + @updated = false + end + + def action(arg=nil) + if arg != nil + arg = arg.to_sym + end + set_or_return( + @action, + arg, + { + :action => arg, + }, + { + :action => { + :equal_to => @allowed_actions, + } + } + ) end def name(name=nil) diff --git a/lib/chef/resource/directory.rb b/lib/chef/resource/directory.rb new file mode 100644 index 0000000000..b03d98fe9b --- /dev/null +++ b/lib/chef/resource/directory.rb @@ -0,0 +1,89 @@ +# +# 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 + class Resource + class Directory < Chef::Resource::File + + def initialize(name, collection=nil) + @resource_name = :file + super(name, collection) + @path = name + @action = "create" + end + + def action(arg=nil) + set_if_args(@action, arg) do + case arg + when "create", "delete", "touch", "nothing" + @action = arg + else + raise ArgumentError, "action must be create, delete, or touch!" + end + end + end + + def group(arg=nil) + set_if_args(@group, arg) do + case arg + when /^([a-z]|[A-Z]|[0-9]|_|-)+$/, Integer + @group = arg + else + raise ArgumentError, "group must match /^([a-z]|[A-Z]|[0-9]|_|-)$/, Integer!" + end + end + end + + def mode(arg=nil) + set_if_args(@mode, arg) do + case "#{arg.to_s}" + when /^\d{3,4}$/ + @mode = arg + else + raise ArgumentError, "mode must be a valid unix file mode - 3 or 4 digets!" + end + end + end + + def owner(arg=nil) + set_if_args(@owner, arg) do + case arg + when /^([a-z]|[A-Z]|[0-9]|_|-)+$/, Integer + @owner = arg + else + raise ArgumentError, "group must match /^([a-z]|[A-Z]|[0-9]|_|-)$/, Integer!" + end + end + end + + def path(arg=nil) + set_if_args(@path, arg) do + case arg + when String + @path = arg + else + raise ArgumentError, "path must be a string!" + end + end + end + + end + end +end
\ No newline at end of file diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb index 59c51ba8d3..3ea75c106b 100644 --- a/lib/chef/resource/file.rb +++ b/lib/chef/resource/file.rb @@ -18,6 +18,7 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # + class Chef class Resource class File < Chef::Resource @@ -28,83 +29,97 @@ class Chef @path = name @backup = true @action = "create" + @allowed_actions.push(:create, :delete, :touch) end def backup(arg=nil) - set_if_args(@backup, arg) do - case arg - when true, false, Integer - @backup = arg - else - raise ArgumentError, "backup must be true, false, or a number!" - end - end + set_or_return( + @backup, + arg, + { + :backup => arg, + }, + { + :backup => { + :kind_of => [ Integer, TrueClass, FalseClass ], + } + } + ) end - + def checksum(arg=nil) - set_if_args(@checksum, arg) do - case arg - when /^[a-zA-Z0-9]{32}$/ # md5sum - @checksum = arg - else - raise ArgumentError, "checksum must be an md5sum!" - end - end + set_or_return( + @checksum, + arg, + { + :checksum => arg, + }, + { + :checksum => { + :regex => /^[a-zA-Z0-9]{32}$/, + }, + } + ) end - - def action(arg=nil) - set_if_args(@action, arg) do - case arg - when "create", "delete" - @action = arg - else - raise ArgumentError, "action must be create or delete!" - end - end - end - + def group(arg=nil) - set_if_args(@group, arg) do - case arg - when /^([a-z]|[A-Z]|[0-9]|_|-)+$/, Integer - @group = arg - else - raise ArgumentError, "group must match /^([a-z]|[A-Z]|[0-9]|_|-)$/, Integer!" - end - end + set_or_return( + @group, + arg, + { + :group => arg + }, + { + :group => { + :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ] + } + } + ) end def mode(arg=nil) - set_if_args(@mode, arg) do - case "#{arg.to_s}" - when /^\d{3,4}$/ - @mode = arg - else - raise ArgumentError, "mode must be a valid unix file mode - 3 or 4 digets!" - end - end + set_or_return( + @mode, + arg, + { + :mode => arg + }, + { + :mode => { + :regex => /^\d{3,4}$/ + } + } + ) end def owner(arg=nil) - set_if_args(@owner, arg) do - case arg - when /^([a-z]|[A-Z]|[0-9]|_|-)+$/, Integer - @owner = arg - else - raise ArgumentError, "group must match /^([a-z]|[A-Z]|[0-9]|_|-)$/, Integer!" - end - end + set_or_return( + @owner, + arg, + { + :owner => arg, + }, + { + :owner => { + :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ] + } + } + ) end def path(arg=nil) - set_if_args(@path, arg) do - case arg - when String - @path = arg - else - raise ArgumentError, "path must be a string!" - end - end + set_or_return( + @path, + arg, + { + :path => arg, + }, + { + :path => { + :kind_of => String, + }, + } + ) end end diff --git a/lib/chef/runner.rb b/lib/chef/runner.rb new file mode 100644 index 0000000000..60f3082e88 --- /dev/null +++ b/lib/chef/runner.rb @@ -0,0 +1,93 @@ +# +# 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 +# + +require File.join(File.dirname(__FILE__), "mixin", "params_validate") + +class Chef + class Runner + + include Chef::Mixin::ParamsValidate + + def initialize(node, collection) + validate( + { + :node => node, + :collection => collection, + }, + { + :node => { + :kind_of => Chef::Node, + }, + :collection => { + :kind_of => Chef::ResourceCollection, + }, + } + ) + @node = node + @collection = collection + end + + def build_provider(resource) + provider_klass = resource.provider + if provider_klass == nil + provider_klass = Chef::Platform.find_provider_for_node(@node, resource) + end + Chef::Log.debug("#{resource} using #{provider_klass.to_s}") + provider = provider_klass.new(@node, resource) + end + + def converge + start_time = Time.now + Chef::Log.info("Starting Chef Run") + delayed_actions = Array.new + + @collection.each do |resource| + Chef::Log.debug("Processing #{resource}") + provider = build_provider(resource) + provider.load_current_resource + provider.send("action_#{resource.action}") + if resource.updated + resource.actions.each_key do |action| + if resource.actions[action].has_key?(:immediate) + resource.actions[action][:immediate].each do |r| + Chef::Log.info("#{resource} sending action #{action} to #{r} (immediate)") + build_provider(r).send("action_#{action}") + end + end + if resource.actions[action].has_key?(:delayed) + resource.actions[action][:delayed].each do |r| + delayed_actions << lambda { + Chef::Log.info("#{resource} sending action #{action} to #{r} (delayed)") + build_provider(r).send("action_#{action}") + } + end + end + end + end + end + + # Run all our :delayed actions + delayed_actions.each { |da| da.call } + end_time = Time.now + Chef::Log.info("Chef Run complete in #{end_time - start_time} seconds") + true + end + end +end
\ No newline at end of file diff --git a/spec/lib/chef/provider/easy.rb b/spec/lib/chef/provider/easy.rb new file mode 100644 index 0000000000..b637efa768 --- /dev/null +++ b/spec/lib/chef/provider/easy.rb @@ -0,0 +1,37 @@ +# +# 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 + class Provider + class Easy < Chef::Provider + def load_current_resource + true + end + + def action_sell + true + end + + def action_buy + true + end + end + end +end
\ No newline at end of file diff --git a/spec/lib/chef/provider/snakeoil.rb b/spec/lib/chef/provider/snakeoil.rb new file mode 100644 index 0000000000..0486402686 --- /dev/null +++ b/spec/lib/chef/provider/snakeoil.rb @@ -0,0 +1,37 @@ +# +# 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 + class Provider + class SnakeOil < Chef::Provider + def load_current_resource + true + end + + def action_sell + true + end + + def action_buy + true + end + end + end +end
\ No newline at end of file diff --git a/spec/lib/chef/resource/cat.rb b/spec/lib/chef/resource/cat.rb index 5b66c9cabd..a0456d6b45 100644 --- a/spec/lib/chef/resource/cat.rb +++ b/spec/lib/chef/resource/cat.rb @@ -21,9 +21,12 @@ class Chef class Resource class Cat < Chef::Resource + attr_accessor :action + def initialize(name, collection=nil) @resource_name = :cat super(name, collection) + @action = "sell" end def pretty_kitty(arg=nil) diff --git a/spec/unit/chef_spec.rb b/spec/unit/chef_spec.rb index cfb6cd4dac..de8042552a 100644 --- a/spec/unit/chef_spec.rb +++ b/spec/unit/chef_spec.rb @@ -24,4 +24,4 @@ describe Chef do it "should have a version defined" do Chef::VERSION.should match(/(\d+)\.(\d+)\.(\d+)/) end -end
\ No newline at end of file +end diff --git a/spec/unit/compile_spec.rb b/spec/unit/compile_spec.rb index 49d5930acb..b6cde6582c 100644 --- a/spec/unit/compile_spec.rb +++ b/spec/unit/compile_spec.rb @@ -35,7 +35,7 @@ describe Chef::Compile do end it "should have a Chef::ResourceCollection" do - @compile.resource_collection.should be_a_kind_of(Chef::ResourceCollection) + @compile.collection.should be_a_kind_of(Chef::ResourceCollection) end it "should have a hash of Definitions" do @@ -58,11 +58,11 @@ describe Chef::Compile do @compile.load_node("compile") @compile.load_definitions lambda { @compile.load_recipes }.should_not raise_error - @compile.resource_collection[0].to_s.should == "cat[einstein]" - @compile.resource_collection[1].to_s.should == "cat[loulou]" - @compile.resource_collection[2].to_s.should == "cat[birthday]" - @compile.resource_collection[3].to_s.should == "cat[peanut]" - @compile.resource_collection[4].to_s.should == "cat[fat peanut]" + @compile.collection[0].to_s.should == "cat[einstein]" + @compile.collection[1].to_s.should == "cat[loulou]" + @compile.collection[2].to_s.should == "cat[birthday]" + @compile.collection[3].to_s.should == "cat[peanut]" + @compile.collection[4].to_s.should == "cat[fat peanut]" end end
\ No newline at end of file diff --git a/spec/unit/log/formatter_spec.rb b/spec/unit/log/formatter_spec.rb index e7518ead87..6651feab2e 100644 --- a/spec/unit/log/formatter_spec.rb +++ b/spec/unit/log/formatter_spec.rb @@ -44,4 +44,9 @@ describe Chef::Log::Formatter do @formatter.call("monkey", Time.new, "test", "mos def").should == "[#{time.rfc2822}] monkey: mos def\n" end + it "should allow you to turn the time on and off in the output" do + Chef::Log::Formatter.show_time = false + @formatter.call("monkey", Time.new, "test", "mos def").should == "monkey: mos def\n" + end + end
\ No newline at end of file diff --git a/spec/unit/mixin/params_validate_spec.rb b/spec/unit/mixin/params_validate_spec.rb index 3ade2b3c2d..2790f2b14a 100644 --- a/spec/unit/mixin/params_validate_spec.rb +++ b/spec/unit/mixin/params_validate_spec.rb @@ -295,4 +295,37 @@ describe Chef::Mixin::ParamsValidate do @vo.validate({ "one" => "two" }, { :one => { :regex => /^two$/ }}) }.should_not raise_error(ArgumentError) end + + it "should allow an array to kind_of" do + lambda { + @vo.validate( + {:one => "string"}, + { + :one => { + :kind_of => [ String, Array ] + } + } + ) + }.should_not raise_error(ArgumentError) + lambda { + @vo.validate( + {:one => ["string"]}, + { + :one => { + :kind_of => [ String, Array ] + } + } + ) + }.should_not raise_error(ArgumentError) + lambda { + @vo.validate( + {:one => Hash.new}, + { + :one => { + :kind_of => [ String, Array ] + } + } + ) + }.should raise_error(ArgumentError) + end end
\ No newline at end of file diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index c72aea0b7d..0ea4d864d4 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -171,4 +171,9 @@ describe Chef::Node do list[2].should == "test" end + it "should turn into a string like node[name]" do + @node.name("airplane") + @node.to_s.should eql("node[airplane]") + end + end
\ No newline at end of file diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb new file mode 100644 index 0000000000..4f0a5e5288 --- /dev/null +++ b/spec/unit/platform_spec.rb @@ -0,0 +1,211 @@ +# +# 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 +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) + +describe Chef::Platform do + before(:each) do + Chef::Platform.platforms = { + :darwin => { + "9.2.2" => { + :file => "darwinian", + :else => "thing" + }, + :default => { + :file => "old school", + :snicker => "snack" + } + }, + :mars_volta => { + }, + :default => { + :file => Chef::Provider::File, + :pax => "brittania", + :cat => "nice" + } + } + end + + it "should allow you to look up a platform by name and version, returning the provider map for it" do + pmap = Chef::Platform.find("Darwin", "9.2.2") + pmap.should be_a_kind_of(Hash) + pmap[:file].should eql("darwinian") + end + + it "should use the default providers for an os if the specific version does not exist" do + pmap = Chef::Platform.find("Darwin", "1") + pmap.should be_a_kind_of(Hash) + pmap[:file].should eql("old school") + end + + it "should use the default providers if the os doesn't give me a default, but does exist" do + pmap = Chef::Platform.find("mars_volta", "1") + pmap.should be_a_kind_of(Hash) + pmap[:file].should eql(Chef::Provider::File) + end + + it "should use the default provider if the os does not exist" do + pmap = Chef::Platform.find("AIX", "1") + pmap.should be_a_kind_of(Hash) + pmap[:file].should eql(Chef::Provider::File) + end + + it "should merge the defaults for an os with the specific version" do + pmap = Chef::Platform.find("Darwin", "9.2.2") + pmap[:file].should eql("darwinian") + pmap[:snicker].should eql("snack") + end + + it "should merge the defaults for an os with the universal defaults" do + pmap = Chef::Platform.find("Darwin", "9.2.2") + pmap[:file].should eql("darwinian") + pmap[:pax].should eql("brittania") + end + + it "should allow you to look up a provider for a platform directly by symbol" do + Chef::Platform.find_provider("Darwin", "9.2.2", :file).should eql("darwinian") + end + + it "should raise an exception if a provider cannot be found for a resource type" do + lambda { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.should raise_error(ArgumentError) + end + + it "should look up a provider for a resource with a Chef::Resource object" do + kitty = Chef::Resource::Cat.new("loulou") + Chef::Platform.find_provider("Darwin", "9.2.2", kitty) + end + + it "should look up a provider with a node and a Chef::Resource object" do + kitty = Chef::Resource::Cat.new("loulou") + node = Chef::Node.new + node.name("Intel") + node.operatingsystem("Darwin") + node.operatingsystemversion("9.2.2") + Chef::Platform.find_provider_for_node(node, kitty).should eql("nice") + end + + it "should prefer lsbdistid over operatingsystem when looking up via node" do + kitty = Chef::Resource::Cat.new("loulou") + node = Chef::Node.new + node.name("Intel") + node.operatingsystem("Darwin") + node.operatingsystemversion("9.2.2") + node.lsbdistid("Not Linux") + Chef::Platform.set( + :platform => :not_linux, + :resource => :cat, + :provider => "bourbon" + ) + Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon") + end + + it "should prefer macosx_productnmae over operatingsystem when looking up via node" do + kitty = Chef::Resource::Cat.new("loulou") + node = Chef::Node.new + node.name("Intel") + node.operatingsystem("Darwin") + node.operatingsystemversion("9.2.2") + node.macosx_productname("Mac OS X") + Chef::Platform.set( + :platform => :mac_os_x, + :resource => :cat, + :provider => "bourbon" + ) + Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon") + end + + it "should prefer lsbdistrelease over operatingsystem when looking up via node" do + kitty = Chef::Resource::Cat.new("loulou") + node = Chef::Node.new + node.name("Intel") + node.operatingsystem("Darwin") + node.operatingsystemversion("9.2.2") + node.lsbdistrelease("10") + Chef::Platform.set( + :platform => :darwin, + :version => "10", + :resource => :cat, + :provider => "bourbon" + ) + Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon") + end + + it "should prefer macosx_productversion over operatingsystem when looking up via node" do + kitty = Chef::Resource::Cat.new("loulou") + node = Chef::Node.new + node.name("Intel") + node.operatingsystem("Darwin") + node.operatingsystemversion("9.2.2") + node.macosx_productversion("10") + Chef::Platform.set( + :platform => :darwin, + :version => "10", + :resource => :cat, + :provider => "bourbon" + ) + Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon") + end + + it "should update the provider map with map" do + Chef::Platform.set( + :platform => :darwin, + :version => "9.2.2", + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:darwin]["9.2.2"][:file].should eql("masterful") + Chef::Platform.set( + :platform => :darwin, + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:darwin][:default][:file].should eql("masterful") + Chef::Platform.set( + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:default][:file].should eql("masterful") + + Chef::Platform.set( + :platform => :hero, + :version => "9.2.2", + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:hero]["9.2.2"][:file].should eql("masterful") + + Chef::Platform.set( + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:default][:file].should eql("masterful") + + Chef::Platform.platforms = {} + + Chef::Platform.set( + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:default][:file].should eql("masterful") + + end + + +end
\ No newline at end of file diff --git a/spec/unit/provider/file_spec.rb b/spec/unit/provider/file_spec.rb index 4a98e88df0..3b894ad02a 100644 --- a/spec/unit/provider/file_spec.rb +++ b/spec/unit/provider/file_spec.rb @@ -171,12 +171,14 @@ describe Chef::Provider::File do @provider.load_current_resource @provider.new_resource.stub!(:owner).and_return(9982398) @provider.new_resource.stub!(:group).and_return(9982398) + @provider.new_resource.stub!(:mode).and_return(0755) @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo") File.stub!(:chown).and_return(1) File.should_receive(:chown).with(nil, 9982398, @provider.new_resource.path) File.stub!(:chown).and_return(1) File.should_receive(:chown).with(9982398, nil, @provider.new_resource.path) File.stub!(:open).and_return(1) + File.should_receive(:chmod).with(0755, @provider.new_resource.path).and_return(1) File.should_receive(:open).with(@provider.new_resource.path, "w+") @provider.action_create end @@ -190,4 +192,21 @@ describe Chef::Provider::File do @provider.action_delete end + it "should raise an error if it cannot delete the file" do + @provider.load_current_resource + @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo") + File.should_receive("exists?").with(@provider.new_resource.path).and_return(false) + lambda { @provider.action_delete }.should raise_error() + end + + it "should update the atime/mtime on action_touch" do + @provider.load_current_resource + @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo") + File.should_receive(:utime).once.and_return(1) + File.stub!(:open).and_return(1) + File.stub!(:chown).and_return(1) + File.stub!(:chmod).and_return(1) + @provider.action_touch + end + end
\ No newline at end of file diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb index 71cc1a47e3..6a28120478 100644 --- a/spec/unit/provider_spec.rb +++ b/spec/unit/provider_spec.rb @@ -43,4 +43,8 @@ describe Chef::Provider do it "should have nil for current_resource by default" do @provider.current_resource.should eql(nil) end + + it "should return true for action_nothing" do + @provider.action_nothing.should eql(true) + end end
\ No newline at end of file diff --git a/spec/unit/resource/directory_spec.rb b/spec/unit/resource/directory_spec.rb new file mode 100644 index 0000000000..18fffb01ff --- /dev/null +++ b/spec/unit/resource/directory_spec.rb @@ -0,0 +1,81 @@ +# 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 +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Resource::Directory do + + before(:each) do + @resource = Chef::Resource::Directory.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::Directory" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::File) + @resource.should be_a_kind_of(Chef::Resource::Directory) + end + + it "should have a name" do + @resource.name.should eql("fakey_fakerton") + end + + it "should have a default action of 'create'" do + @resource.action.should eql("create") + end + + it "should accept create or delete for action" do + lambda { @resource.action "create" }.should_not raise_error(ArgumentError) + lambda { @resource.action "delete" }.should_not raise_error(ArgumentError) + lambda { @resource.action "blues" }.should raise_error(ArgumentError) + end + + it "should accept a group name or id for group" do + lambda { @resource.group "root" }.should_not raise_error(ArgumentError) + lambda { @resource.group 123 }.should_not raise_error(ArgumentError) + lambda { @resource.group "root*goo" }.should raise_error(ArgumentError) + end + + it "should accept a valid unix file mode" do + lambda { @resource.mode 0444 }.should_not raise_error(ArgumentError) + lambda { @resource.mode 444 }.should_not raise_error(ArgumentError) + lambda { @resource.mode 4 }.should raise_error(ArgumentError) + end + + it "should accept a user name or id for owner" do + lambda { @resource.owner "root" }.should_not raise_error(ArgumentError) + lambda { @resource.owner 123 }.should_not raise_error(ArgumentError) + lambda { @resource.owner "root*goo" }.should raise_error(ArgumentError) + end + + it "should use the object name as the path by default" do + @resource.path.should eql("fakey_fakerton") + end + + it "should accept a string as the path" do + lambda { @resource.path "/tmp" }.should_not raise_error(ArgumentError) + lambda { @resource.path Hash.new }.should raise_error(ArgumentError) + end + + it "should allow you to have specify whether the action is recursive with true/false" do + lambda { @resource.recursive true }.should_not raise_error(ArgumentError) + lambda { @resource.recursive false }.should_not raise_error(ArgumentError) + lambda { @resource.recursive "monkey" }.should raise_error(ArgumentError) + end + +end
\ No newline at end of file diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index d6db1f2d74..be9a7cc3aa 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -129,6 +129,7 @@ describe Chef::Resource do @resource.noop(@resource.is(true)) @resource.noop.should eql(true) end + # it "should serialize to yaml" do # yaml_output = <<-DESC diff --git a/spec/unit/runner_spec.rb b/spec/unit/runner_spec.rb new file mode 100644 index 0000000000..2a5c613cc7 --- /dev/null +++ b/spec/unit/runner_spec.rb @@ -0,0 +1,105 @@ +# +# 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 +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) + +describe Chef::Runner do + before(:each) do + @mock_node = mock("Node", :null_object => true) + @mock_collection = mock("Resource Collection", :null_object => true) + @mock_provider = mock("Provider", :null_object => true) + @mock_resource = mock("Resource", :null_object => true) + new_runner + end + + it "should require a Node and a ResourceCollection" do + @mock_node.should_receive(:kind_of?).once.and_return(true) + @mock_collection.should_receive(:kind_of?).once.and_return(true) + runner = Chef::Runner.new(@mock_node, @mock_collection) + runner.should be_a_kind_of(Chef::Runner) + end + + it "should raise an exception if you pass the wrong kind of object to new" do + @mock_node.stub!(:kind_of?).and_return(false) + @mock_collecton.stub!(:kind_of?).and_return(false) + lambda { Chef::Runner.new(@mock_node, @mock_collection) }.should raise_error(ArgumentError) + end + + it "should pass each resource in the collection to a provider" do + @collection.should_receive(:each).once + @runner.converge + end + + it "should use the provider specified by the resource (if it has one)" do + provider = Chef::Provider::Easy.new(@node, @collection[0]) + @collection[0].should_receive(:provider).once.and_return(Chef::Provider::Easy) + Chef::Provider::Easy.should_receive(:new).once.and_return(provider) + @runner.converge + end + + it "should use the platform provider if it has one" do + Chef::Platform.should_receive(:find_provider_for_node).once.and_return(Chef::Provider::SnakeOil) + @runner.converge + end + + it "should run the action for each resource" do + Chef::Platform.should_receive(:find_provider_for_node).once.and_return(Chef::Provider::SnakeOil) + provider = Chef::Provider::SnakeOil.new(@node, @collection[0]) + provider.should_receive(:action_sell).once.and_return(true) + Chef::Provider::SnakeOil.should_receive(:new).once.and_return(provider) + @runner.converge + end + + it "should execute immediate actions on changed resources" do + Chef::Platform.should_receive(:find_provider_for_node).exactly(3).times.and_return(Chef::Provider::SnakeOil) + provider = Chef::Provider::SnakeOil.new(@node, @collection[0]) + Chef::Provider::SnakeOil.should_receive(:new).exactly(3).times.and_return(provider) + @collection << Chef::Resource::Cat.new("peanut", @collection) + @collection[1].notifies :buy, @collection[0], :immediately + @collection[1].updated = true + provider.should_receive(:action_buy).once.and_return(true) + @runner.converge + end + + it "should execute delayed actions on changed resources" do + Chef::Platform.should_receive(:find_provider_for_node).exactly(3).times.and_return(Chef::Provider::SnakeOil) + provider = Chef::Provider::SnakeOil.new(@node, @collection[0]) + Chef::Provider::SnakeOil.should_receive(:new).exactly(3).times.and_return(provider) + @collection << Chef::Resource::Cat.new("peanut", @collection) + @collection[1].notifies :buy, @collection[0], :delayed + @collection[1].updated = true + provider.should_receive(:action_buy).once.and_return(true) + @runner.converge + end + + def new_runner + @node = Chef::Node.new + @node.name "latte" + @node.operatingsystem "mac_os_x" + @node.operatingsystemversion "10.5.1" + @collection = Chef::ResourceCollection.new() + @collection << Chef::Resource::Cat.new("loulou", @collection) + Chef::Platform.set( + :resource => :cat, + :provider => Chef::Provider::SnakeOil + ) + @runner = Chef::Runner.new(@node, @collection) + end +end
\ No newline at end of file |