lib/chefspec/extensions/chef/resource.rb
require "chef/resource" require "chef/version" require_relative "../../api" # # Three concerns: # - no-op'ing so that the action does not run the provider # - tracking the actions that were performed # - auto registering helper methods # module ChefSpec::Extensions::Chef::Resource # # Hooks for the stubs_for system # def initialize(*args, &block) super(*args, &block) if $CHEFSPEC_MODE # Here be dragons. # If we're directly inside a `load_current_resource`, this is probably # something like `new_resource.class.new` so we want to call this a current_resource, # Otherwise it's probably a normal resource instantiation. mode = :resource mode = :current_value if caller.any? { |x| x.include?("`load_current_resource'") || x.include?("`load_after_resource'") } ChefSpec::API::StubsFor.setup_stubs_for(self, mode) end end def dup return super unless $CHEFSPEC_MODE # Also here be dragons. super.tap do |dup_resource| # We're directly inside a load_current_resource, which is probably via # the load_current_value DSL system, so call this a current resource. ChefSpec::API::StubsFor.setup_stubs_for(dup_resource, :current_value) if caller.any? { |x| x.include?("`load_current_resource'") || x.include?("`load_after_resource'") } end end # mix of no-op and tracking concerns def run_action(action, notification_type = nil, notifying_resource = nil) return super unless $CHEFSPEC_MODE resolve_notification_references validate_action(action) Chef::Log.info("Processing #{self} action #{action} (#{defined_at})") ChefSpec::Coverage.add(self) unless should_skip?(action) if node.runner.step_into?(self) instance_eval { @not_if = []; @only_if = [] } super end if node.runner.compiling? perform_action(action, compile_time: true) else perform_action(action, converge_time: true) end end end # # Defang shell_out and friends so it can never run. # if ChefSpec::API::StubsFor::HAS_SHELLOUT_COMPACTED.satisfied_by?(Gem::Version.create(Chef::VERSION)) def shell_out_compacted(*args) return super unless $CHEFSPEC_MODE raise ChefSpec::Error::ShellOutNotStubbed.new(args: args, type: "resource", resource: self) end def shell_out_compacted!(*args) return super unless $CHEFSPEC_MODE shell_out_compacted(*args).tap(&:error!) end else def shell_out(*args) return super unless $CHEFSPEC_MODE raise ChefSpec::Error::ShellOutNotStubbed.new(args: args, type: "resource", resource: self) end end # # tracking # def perform_action(action, options = {}) @performed_actions ||= {} @performed_actions[action.to_sym] ||= {} @performed_actions[action.to_sym].merge!(options) end def performed_action(action) @performed_actions ||= {} @performed_actions[action.to_sym] end def performed_action?(action) if action == :nothing performed_actions.empty? else !!performed_action(action) end end def performed_actions @performed_actions ||= {} @performed_actions.keys end # # auto-registration # def self.prepended(base) class << base prepend ClassMethods end end module ClassMethods # XXX: kind of a crappy way to find all the names of a resource def provides_names @provides_names ||= [] end def resource_name(name = ::Chef::NOT_PASSED) unless name == ::Chef::NOT_PASSED provides_names << name unless provides_names.include?(name) inject_actions(*allowed_actions) end super end def provides(name, **options, &block) provides_names << name unless provides_names.include?(name) inject_actions(*allowed_actions) super end def action(sym, description: nil, &block) inject_actions(sym) super(sym, &block) end def allowed_actions(*actions) inject_actions(*actions) unless actions.empty? super end def allowed_actions=(value) inject_actions(*Array(value)) super end private def inject_method(method, resource_name, action) unless ChefSpec::API.respond_to?(method) ChefSpec::API.send(:define_method, method) do |name| ChefSpec::Matchers::ResourceMatcher.new(resource_name, action, name) end end end def inject_actions(*actions) provides_names.each do |resource_name| next unless resource_name ChefSpec.define_matcher(resource_name) actions.each do |action| inject_method(:"#{action}_#{resource_name}", resource_name, action) if action == :create_if_missing inject_method(:"create_#{resource_name}_if_missing", resource_name, action) end end end end end end Chef::Resource.prepend(ChefSpec::Extensions::Chef::Resource)