diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb index 5092ecd49..ee67d3edc 100644 --- a/lib/puppet/provider.rb +++ b/lib/puppet/provider.rb @@ -1,294 +1,290 @@ # The container class for implementations. class Puppet::Provider include Puppet::Util::Execution include Puppet::Util include Puppet::Util::Errors include Puppet::Util::Warnings extend Puppet::Util::Warnings require 'puppet/provider/confiner' + require 'puppet/provider/command' extend Puppet::Provider::Confiner Puppet::Util.logmethods(self, true) class << self # Include the util module so we have access to things like 'which' include Puppet::Util, Puppet::Util::Docs include Puppet::Util::Logging attr_accessor :name # The source parameter exists so that providers using the same # source can specify this, so reading doesn't attempt to read the # same package multiple times. attr_writer :source # LAK 2007-05-09: Keep the model stuff around for backward compatibility attr_reader :model attr_accessor :resource_type attr_writer :doc end # LAK 2007-05-09: Keep the model stuff around for backward compatibility attr_reader :model attr_accessor :resource def self.command(name) name = symbolize(name) if defined?(@commands) and command = @commands[name] # nothing elsif superclass.respond_to? :command and command = superclass.command(name) # nothing else raise Puppet::DevError, "No command #{name} defined for provider #{self.name}" end which(command) end # Define commands that are not optional. def self.commands(hash) optional_commands(hash) do |name, path| confine :exists => path, :for_binary => true end end # Is the provided feature a declared feature? def self.declared_feature?(name) defined?(@declared_features) and @declared_features.include?(name) end # Does this implementation match all of the default requirements? If # defaults are empty, we return false. def self.default? return false if @defaults.empty? if @defaults.find do |fact, values| values = [values] unless values.is_a? Array if fval = Facter.value(fact).to_s and fval != "" fval = fval.to_s.downcase.intern else return false end # If any of the values match, we're a default. if values.find do |value| fval == value.to_s.downcase.intern end false else true end end return false else return true end end # Store how to determine defaults. def self.defaultfor(hash) hash.each do |d,v| @defaults[d] = v end end def self.specificity (@defaults.length * 100) + ancestors.select { |a| a.is_a? Class }.length end def self.initvars @defaults = {} @commands = {} end # The method for returning a list of provider instances. Note that it returns providers, preferably with values already # filled in, not resources. def self.instances raise Puppet::DevError, "Provider #{self.name} has not defined the 'instances' class method" end # Create the methods for a given command. def self.make_command_methods(name, options) # Now define a method for that command unless singleton_class.method_defined?(name) meta_def(name) do |*args| raise Puppet::Error, "Command #{name} is missing" unless command(name) - if args.empty? - cmd = [command(name)] - else - cmd = [command(name)] + args - end # This might throw an ExecutionFailure, but the system above # will catch it, if so. - return execute(cmd, options) + return Puppet::Provider::Command.new(command(name), options).execute(Puppet::Util::Execution, *args) end # And then define an instance method that just calls the class method. # We need both, so both instances and classes can easily run the commands. unless method_defined?(name) define_method(name) do |*args| self.class.send(name, *args) end end end end # Create getter/setter methods for each property our resource type supports. # They all get stored in @property_hash. This method is useful # for those providers that use prefetch and flush. def self.mkmodelmethods Puppet.deprecation_warning "Provider.mkmodelmethods is deprecated; use Provider.mk_resource_methods" mk_resource_methods end # Create getter/setter methods for each property our resource type supports. # They all get stored in @property_hash. This method is useful # for those providers that use prefetch and flush. def self.mk_resource_methods [resource_type.validproperties, resource_type.parameters].flatten.each do |attr| attr = symbolize(attr) next if attr == :name define_method(attr) do @property_hash[attr] || :absent end define_method(attr.to_s + "=") do |val| @property_hash[attr] = val end end end self.initvars # Define one or more binaries we'll be using. If a block is passed, yield the name # and path to the block (really only used by 'commands'). def self.optional_commands(hash) hash.each do |name, target| name = symbolize(name) path = target.is_a?(Hash) ? target[:path] : target options = target.is_a?(Hash) ? target[:options] : {} @commands[name] = path yield(name, path) if block_given? # Now define the class and instance methods. make_command_methods(name, options) end end # Retrieve the data source. Defaults to the provider name. def self.source @source ||= self.name end # Does this provider support the specified parameter? def self.supports_parameter?(param) if param.is_a?(Class) klass = param else unless klass = resource_type.attrclass(param) raise Puppet::DevError, "'#{param}' is not a valid parameter for #{resource_type.name}" end end return true unless features = klass.required_features !!satisfies?(*features) end # def self.to_s # unless defined?(@str) # if self.resource_type # @str = "#{resource_type.name} provider #{self.name}" # else # @str = "unattached provider #{self.name}" # end # end # @str # end dochook(:defaults) do if @defaults.length > 0 return "Default for " + @defaults.collect do |f, v| "`#{f}` == `#{[v].flatten.join(', ')}`" end.join(" and ") + "." end end dochook(:commands) do if @commands.length > 0 return "Required binaries: " + @commands.collect do |n, c| "`#{c}`" end.join(", ") + "." end end dochook(:features) do if features.length > 0 return "Supported features: " + features.collect do |f| "`#{f}`" end.join(", ") + "." end end # Remove the reference to the resource, so GC can clean up. def clear @resource = nil @model = nil end # Retrieve a named command. def command(name) self.class.command(name) end # Get a parameter value. def get(param) @property_hash[symbolize(param)] || :absent end def initialize(resource = nil) if resource.is_a?(Hash) # We don't use a duplicate here, because some providers (ParsedFile, at least) # use the hash here for later events. @property_hash = resource elsif resource @resource = resource # LAK 2007-05-09: Keep the model stuff around for backward compatibility @model = resource @property_hash = {} else @property_hash = {} end end def name if n = @property_hash[:name] return n elsif self.resource resource.name else raise Puppet::DevError, "No resource and no name in property hash in #{self.class.name} instance" end end # Set passed params as the current values. def set(params) params.each do |param, value| @property_hash[symbolize(param)] = value end end def to_s "#{@resource}(provider=#{self.class.name})" end # Make providers comparable. include Comparable def <=>(other) # We can only have ordering against other providers. return nil unless other.is_a? Puppet::Provider # Otherwise, order by the providers class name. return self.class.name <=> other.class.name end end diff --git a/lib/puppet/provider/command.rb b/lib/puppet/provider/command.rb index 8e56aa24a..c47e24089 100644 --- a/lib/puppet/provider/command.rb +++ b/lib/puppet/provider/command.rb @@ -1,10 +1,10 @@ class Puppet::Provider::Command def initialize(executable, options = {}) @executable = executable @options = options end - def execute(executor) - executor.execute([@executable], @options) + def execute(executor, *args) + executor.execute([@executable] + args, @options) end end diff --git a/spec/unit/provider/command.rb b/spec/unit/provider/command.rb index 7f1b21d58..a981580b6 100755 --- a/spec/unit/provider/command.rb +++ b/spec/unit/provider/command.rb @@ -1,21 +1,31 @@ require 'spec_helper' require 'puppet/provider/command' describe Puppet::Provider::Command do + let(:the_options) { { :option => 1 } } + let(:no_options) { {} } + let(:executable) { "/foo" } let(:executor) { mock('executor') } - it "exectutes a simple command" do - executor.expects(:execute).with(["/foo"], {}) + it "executes a simple command" do + executor.expects(:execute).with([executable], no_options) - command = Puppet::Provider::Command.new("/foo") + command = Puppet::Provider::Command.new(executable) command.execute(executor) end - it "exectutes a command with extra options" do - executor.expects(:execute).with(["/foo"], { :option => 1}) + it "executes a command with extra options" do + executor.expects(:execute).with([executable], the_options) - command = Puppet::Provider::Command.new("/foo", { :option => 1 }) + command = Puppet::Provider::Command.new(executable, the_options) command.execute(executor) end + + it "executes a command with arguments" do + executor.expects(:execute).with([executable, "arg1", "arg2"], no_options) + + command = Puppet::Provider::Command.new(executable) + command.execute(executor, "arg1", "arg2") + end end diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb index defe921b8..74445fbf7 100755 --- a/spec/unit/provider/package/pacman_spec.rb +++ b/spec/unit/provider/package/pacman_spec.rb @@ -1,263 +1,264 @@ #!/usr/bin/env rspec require 'spec_helper' require 'stringio' provider = Puppet::Type.type(:package).provider(:pacman) describe provider do let(:no_extra_options) { {} } + let(:executor) { Puppet::Util::Execution } before do provider.stubs(:command).with(:pacman).returns('/usr/bin/pacman') @resource = Puppet::Type.type(:package).new(:name => 'package') @provider = provider.new(@resource) end describe "when installing" do before do @provider.stubs(:query).returns({ :ensure => '1.0' }) end it "should call pacman to install the right package quietly" do - provider. + executor. expects(:execute). at_least_once. with(["/usr/bin/pacman", "--noconfirm", "--noprogressbar", "-Sy", @resource[:name]], no_extra_options). returns "" @provider.install end it "should raise an ExecutionFailure if the installation failed" do - provider.stubs(:execute).returns("") + executor.stubs(:execute).returns("") @provider.expects(:query).returns(nil) lambda { @provider.install }.should raise_exception(Puppet::ExecutionFailure) end context "when :source is specified" do before :each do @install = sequence("install") end context "recognizable by pacman" do %w{ /some/package/file http://some.package.in/the/air ftp://some.package.in/the/air }.each do |source| it "should install #{source} directly" do @resource[:source] = source - provider.expects(:execute). + executor.expects(:execute). with(all_of(includes("-Sy"), includes("--noprogressbar")), no_extra_options). in_sequence(@install). returns("") - provider.expects(:execute). + executor.expects(:execute). with(all_of(includes("-U"), includes(source)), no_extra_options). in_sequence(@install). returns("") @provider.install end end end context "as a file:// URL" do before do @package_file = "file:///some/package/file" @actual_file_path = "/some/package/file" @resource[:source] = @package_file end it "should install from the path segment of the URL" do - provider.expects(:execute). + executor.expects(:execute). with(all_of(includes("-Sy"), includes("--noprogressbar"), includes("--noconfirm")), no_extra_options). in_sequence(@install). returns("") - provider.expects(:execute). + executor.expects(:execute). with(all_of(includes("-U"), includes(@actual_file_path)), no_extra_options). in_sequence(@install). returns("") @provider.install end end context "as a puppet URL" do before do @resource[:source] = "puppet://server/whatever" end it "should fail" do lambda { @provider.install }.should raise_error(Puppet::Error) end end context "as a malformed URL" do before do @resource[:source] = "blah://" end it "should fail" do lambda { @provider.install }.should raise_error(Puppet::Error) end end end end describe "when updating" do it "should call install" do @provider.expects(:install).returns("install return value") @provider.update.should == "install return value" end end describe "when uninstalling" do it "should call pacman to remove the right package quietly" do - provider. + executor. expects(:execute). with(["/usr/bin/pacman", "--noconfirm", "--noprogressbar", "-R", @resource[:name]], no_extra_options). returns "" @provider.uninstall end end describe "when querying" do it "should query pacman" do - provider. + executor. expects(:execute). with(["/usr/bin/pacman", "-Qi", @resource[:name]], no_extra_options) @provider.query end it "should return the version" do query_output = <=2.7.1 libfetch>=2.25 pacman-mirrorlist Optional Deps : fakeroot: for makepkg usage as normal user curl: for rankmirrors usage Required By : None Conflicts With : None Replaces : None Installed Size : 2352.00 K Packager : Dan McGee Architecture : i686 Build Date : Sat 22 Jan 2011 03:56:41 PM EST Install Date : Thu 27 Jan 2011 06:45:49 AM EST Install Reason : Explicitly installed Install Script : Yes Description : A library-based package manager with dependency support EOF - provider.expects(:execute).returns(query_output) + executor.expects(:execute).returns(query_output) @provider.query.should == {:ensure => "1.01.3-2"} end it "should return a nil if the package isn't found" do - provider.expects(:execute).returns("") + executor.expects(:execute).returns("") @provider.query.should be_nil end it "should return a hash indicating that the package is missing on error" do - provider.expects(:execute).raises(Puppet::ExecutionFailure.new("ERROR!")) + executor.expects(:execute).raises(Puppet::ExecutionFailure.new("ERROR!")) @provider.query.should == { :ensure => :purged, :status => 'missing', :name => @resource[:name], :error => 'ok', } end end describe "when fetching a package list" do it "should query pacman" do provider.expects(:execpipe).with(["/usr/bin/pacman", '-Q']) provider.instances end it "should return installed packages with their versions" do provider.expects(:execpipe).yields(StringIO.new("package1 1.23-4\npackage2 2.00\n")) packages = provider.instances packages.length.should == 2 packages[0].properties.should == { :provider => :pacman, :ensure => '1.23-4', :name => 'package1' } packages[1].properties.should == { :provider => :pacman, :ensure => '2.00', :name => 'package2' } end it "should return nil on error" do provider.expects(:execpipe).raises(Puppet::ExecutionFailure.new("ERROR!")) provider.instances.should be_nil end it "should warn on invalid input" do provider.expects(:execpipe).yields(StringIO.new("blah")) provider.expects(:warning).with("Failed to match line blah") provider.instances.should == [] end end describe "when determining the latest version" do it "should refresh package list" do get_latest_version = sequence("get_latest_version") - provider. + executor. expects(:execute). in_sequence(get_latest_version). with(['/usr/bin/pacman', '-Sy'], no_extra_options) - provider. + executor. stubs(:execute). in_sequence(get_latest_version). returns("") @provider.latest end it "should get query pacman for the latest version" do get_latest_version = sequence("get_latest_version") - provider. + executor. stubs(:execute). in_sequence(get_latest_version) - provider. + executor. expects(:execute). in_sequence(get_latest_version). with(['/usr/bin/pacman', '-Sp', '--print-format', '%v', @resource[:name]], no_extra_options). returns("") @provider.latest end it "should return the version number from pacman" do - provider. + executor. expects(:execute). at_least_once(). returns("1.00.2-3\n") @provider.latest.should == "1.00.2-3" end end end