diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb index f314fa378..dbf66c93f 100644 --- a/lib/puppet/configurer.rb +++ b/lib/puppet/configurer.rb @@ -1,257 +1,257 @@ # The client for interacting with the puppetmaster config server. require 'sync' require 'timeout' require 'puppet/network/http_pool' require 'puppet/util' class Puppet::Configurer require 'puppet/configurer/fact_handler' require 'puppet/configurer/plugin_handler' include Puppet::Configurer::FactHandler include Puppet::Configurer::PluginHandler # For benchmarking include Puppet::Util attr_reader :compile_time # Provide more helpful strings to the logging that the Agent does def self.to_s "Puppet configuration client" end class << self # Puppetd should only have one instance running, and we need a way # to retrieve it. attr_accessor :instance include Puppet::Util end # How to lock instances of this class. def self.lockfile_path Puppet[:puppetdlockfile] end def clear @catalog.clear(true) if @catalog @catalog = nil end def execute_postrun_command execute_from_setting(:postrun_command) end def execute_prerun_command execute_from_setting(:prerun_command) end # Initialize and load storage def dostorage Puppet::Util::Storage.load @compile_time ||= Puppet::Util::Storage.cache(:configuration)[:compile_time] rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Corrupt state file #{Puppet[:statefile]}: #{detail}" begin ::File.unlink(Puppet[:statefile]) retry rescue => detail raise Puppet::Error.new("Cannot remove #{Puppet[:statefile]}: #{detail}") end end # Just so we can specify that we are "the" instance. def initialize Puppet.settings.use(:main, :ssl, :agent) self.class.instance = self @running = false @splayed = false end # Prepare for catalog retrieval. Downloads everything necessary, etc. def prepare(options) dostorage download_plugins unless options[:skip_plugin_download] end # Get the remote catalog, yo. Returns nil if no catalog can be found. def retrieve_catalog(fact_options) fact_options ||= {} # First try it with no cache, then with the cache. unless (Puppet[:use_cached_catalog] and result = retrieve_catalog_from_cache(fact_options)) or result = retrieve_new_catalog(fact_options) if ! Puppet[:usecacheonfailure] Puppet.warning "Not using cache on failed catalog" return nil end result = retrieve_catalog_from_cache(fact_options) end return nil unless result convert_catalog(result, @duration) end # Convert a plain resource catalog into our full host catalog. def convert_catalog(result, duration) catalog = result.to_ral catalog.finalize catalog.retrieval_duration = duration catalog.write_class_file catalog.write_resource_file catalog end # Retrieve (optionally) and apply a catalog. If a catalog is passed in # the options, then apply that one, otherwise retrieve it. def retrieve_and_apply_catalog(options, fact_options) unless catalog = (options.delete(:catalog) || retrieve_catalog(fact_options)) Puppet.err "Could not retrieve catalog; skipping run" return end report = options[:report] report.configuration_version = catalog.version report.environment = Puppet[:environment] benchmark(:notice, "Finished catalog run") do catalog.apply(options) end report.finalize_report report end # The code that actually runs the catalog. # This just passes any options on to the catalog, # which accepts :tags and :ignoreschedules. def run(options = {}) options[:report] ||= Puppet::Transaction::Report.new("apply") report = options[:report] Puppet::Util::Log.newdestination(report) begin prepare(options) if Puppet::Resource::Catalog.indirection.terminus_class == :rest # This is a bit complicated. We need the serialized and escaped facts, # and we need to know which format they're encoded in. Thus, we # get a hash with both of these pieces of information. fact_options = facts_for_uploading end # set report host name now that we have the fact report.host = Puppet[:node_name_value] begin execute_prerun_command or return nil if retrieve_and_apply_catalog(options, fact_options) report.exit_status else nil end rescue SystemExit,NoMemoryError raise rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Failed to apply catalog: #{detail}" return nil ensure execute_postrun_command or return nil end ensure # Make sure we forget the retained module_directories of any autoload # we might have used. Thread.current[:env_module_directories] = nil end ensure Puppet::Util::Log.close(report) send_report(report) end def send_report(report) puts report.summary if Puppet[:summarize] save_last_run_summary(report) Puppet::Transaction::Report.indirection.save(report) if Puppet[:report] rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not send report: #{detail}" end def save_last_run_summary(report) last_run = Puppet.settings.setting(:lastrunfile) last_run.create = true # force file creation resource = last_run.to_resource resource[:content] = YAML.dump(report.raw_summary) catalog = Puppet::Resource::Catalog.new("last_run_file") catalog.add_resource(resource) ral = catalog.to_ral ral.host_config = false ral.apply rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not save last run local report: #{detail}" end private def self.timeout timeout = Puppet[:configtimeout] case timeout when String if timeout =~ /^\d+$/ timeout = Integer(timeout) else raise ArgumentError, "Configuration timeout must be an integer" end when Integer # nothing else raise ArgumentError, "Configuration timeout must be an integer" end timeout end def execute_from_setting(setting) return true if (command = Puppet[setting]) == "" begin - Puppet::Util.execute([command]) + Puppet::Util::Execution.execute([command]) true rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not run command from #{setting}: #{detail}" false end end def retrieve_catalog_from_cache(fact_options) result = nil @duration = thinmark do result = Puppet::Resource::Catalog.indirection.find(Puppet[:node_name_value], fact_options.merge(:ignore_terminus => true)) end Puppet.notice "Using cached catalog" result rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not retrieve catalog from cache: #{detail}" return nil end def retrieve_new_catalog(fact_options) result = nil @duration = thinmark do result = Puppet::Resource::Catalog.indirection.find(Puppet[:node_name_value], fact_options.merge(:ignore_cache => true)) end result rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not retrieve catalog from remote server: #{detail}" return nil end end diff --git a/lib/puppet/indirector/exec.rb b/lib/puppet/indirector/exec.rb index 63809e0f7..c735de475 100644 --- a/lib/puppet/indirector/exec.rb +++ b/lib/puppet/indirector/exec.rb @@ -1,48 +1,48 @@ require 'puppet/indirector/terminus' require 'puppet/util' class Puppet::Indirector::Exec < Puppet::Indirector::Terminus # Look for external node definitions. def find(request) # Run the command. unless output = query(request.key) return nil end # Translate the output to ruby. output end private # Proxy the execution, so it's easier to test. def execute(command, arguments) - Puppet::Util.execute(command,arguments) + Puppet::Util::Execution.execute(command,arguments) end # Call the external command and see if it returns our output. def query(name) external_command = command # Make sure it's an arry raise Puppet::DevError, "Exec commands must be an array" unless external_command.is_a?(Array) # Make sure it's fully qualified. raise ArgumentError, "You must set the exec parameter to a fully qualified command" unless Puppet::Util.absolute_path?(external_command[0]) # Add our name to it. external_command << name begin output = execute(external_command, :combine => false) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Failed to find #{name} via exec: #{detail}" end if output =~ /\A\s*\Z/ # all whitespace Puppet.debug "Empty response for #{name} from #{self.name} terminus" return nil else return output end end end diff --git a/lib/puppet/parser/functions/generate.rb b/lib/puppet/parser/functions/generate.rb index 226a5b7e1..cf9166f89 100644 --- a/lib/puppet/parser/functions/generate.rb +++ b/lib/puppet/parser/functions/generate.rb @@ -1,37 +1,37 @@ # Runs an external command and returns the results Puppet::Parser::Functions::newfunction(:generate, :type => :rvalue, :doc => "Calls an external command on the Puppet master and returns the results of the command. Any arguments are passed to the external command as arguments. If the generator does not exit with return code of 0, the generator is considered to have failed and a parse error is thrown. Generators can only have file separators, alphanumerics, dashes, and periods in them. This function will attempt to protect you from malicious generator calls (e.g., those with '..' in them), but it can never be entirely safe. No subshell is used to execute generators, so all shell metacharacters are passed directly to the generator.") do |args| raise Puppet::ParseError, "Generators must be fully qualified" unless Puppet::Util.absolute_path?(args[0]) if Puppet.features.microsoft_windows? valid = args[0] =~ /^[a-z]:(?:[\/\\][\w.-]+)+$/i else valid = args[0] =~ /^[-\/\w.]+$/ end unless valid raise Puppet::ParseError, "Generators can only contain alphanumerics, file separators, and dashes" end if args[0] =~ /\.\./ raise Puppet::ParseError, "Can not use generators with '..' in them." end begin - Dir.chdir(File.dirname(args[0])) { Puppet::Util.execute(args) } + Dir.chdir(File.dirname(args[0])) { Puppet::Util::Execution.execute(args) } rescue Puppet::ExecutionFailure => detail raise Puppet::ParseError, "Failed to execute generator #{args[0]}: #{detail}" end end diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb index fc04c1646..3bd608476 100644 --- a/lib/puppet/provider.rb +++ b/lib/puppet/provider.rb @@ -1,281 +1,282 @@ # The container class for implementations. class Puppet::Provider include Puppet::Util + include Puppet::Util::Execution include Puppet::Util::Errors include Puppet::Util::Warnings extend Puppet::Util::Warnings require 'puppet/provider/confiner' 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) # 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) 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, path| name = symbolize(name) @commands[name] = path yield(name, path) if block_given? # Now define the class and instance methods. make_command_methods(name) 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 end diff --git a/lib/puppet/provider/package/dpkg.rb b/lib/puppet/provider/package/dpkg.rb index 878153a38..f0d488f06 100755 --- a/lib/puppet/provider/package/dpkg.rb +++ b/lib/puppet/provider/package/dpkg.rb @@ -1,155 +1,155 @@ require 'puppet/provider/package' Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package do desc "Package management via `dpkg`. Because this only uses `dpkg` and not `apt`, you must specify the source of any packages you want to manage." has_feature :holdable commands :dpkg => "/usr/bin/dpkg" commands :dpkg_deb => "/usr/bin/dpkg-deb" commands :dpkgquery => "/usr/bin/dpkg-query" def self.instances packages = [] # list out all of the packages cmd = "#{command(:dpkgquery)} -W --showformat '${Status} ${Package} ${Version}\\n'" Puppet.debug "Executing '#{cmd}'" - execpipe(cmd) do |process| + Puppet::Util::Execution.execpipe(cmd) do |process| # our regex for matching dpkg output regex = %r{^(\S+) +(\S+) +(\S+) (\S+) (\S*)$} fields = [:desired, :error, :status, :name, :ensure] hash = {} # now turn each returned line into a package object process.each { |line| if hash = parse_line(line) packages << new(hash) end } end packages end self::REGEX = %r{^(\S+) +(\S+) +(\S+) (\S+) (\S*)$} self::FIELDS = [:desired, :error, :status, :name, :ensure] def self.parse_line(line) if match = self::REGEX.match(line) hash = {} self::FIELDS.zip(match.captures) { |field,value| hash[field] = value } hash[:provider] = self.name if hash[:status] == 'not-installed' hash[:ensure] = :purged elsif ['config-files', 'half-installed', 'unpacked', 'half-configured'].include?(hash[:status]) hash[:ensure] = :absent end hash[:ensure] = :held if hash[:desired] == 'hold' else Puppet.warning "Failed to match dpkg-query line #{line.inspect}" return nil end hash end def install unless file = @resource[:source] raise ArgumentError, "You cannot install dpkg packages without a source" end args = [] # We always unhold when installing to remove any prior hold. self.unhold if @resource[:configfiles] == :keep args << '--force-confold' else args << '--force-confnew' end args << '-i' << file dpkg(*args) end def update self.install end # Return the version from the package. def latest output = dpkg_deb "--show", @resource[:source] matches = /^(\S+)\t(\S+)$/.match(output).captures warning "source doesn't contain named package, but #{matches[0]}" unless matches[0].match( Regexp.escape(@resource[:name]) ) matches[1] end def query packages = [] fields = [:desired, :error, :status, :name, :ensure] hash = {} # list out our specific package begin output = dpkgquery( "-W", "--showformat", '${Status} ${Package} ${Version}\\n', @resource[:name] ) rescue Puppet::ExecutionFailure # dpkg-query exits 1 if the package is not found. return {:ensure => :purged, :status => 'missing', :name => @resource[:name], :error => 'ok'} end hash = self.class.parse_line(output) || {:ensure => :absent, :status => 'missing', :name => @resource[:name], :error => 'ok'} if hash[:error] != "ok" raise Puppet::Error.new( "Package #{hash[:name]}, version #{hash[:ensure]} is in error state: #{hash[:error]}" ) end hash end def uninstall dpkg "-r", @resource[:name] end def purge dpkg "--purge", @resource[:name] end def hold self.install begin Tempfile.open('puppet_dpkg_set_selection') { |tmpfile| tmpfile.write("#{@resource[:name]} hold\n") tmpfile.flush execute([:dpkg, "--set-selections"], :stdinfile => tmpfile.path.to_s) } end end def unhold begin Tempfile.open('puppet_dpkg_set_selection') { |tmpfile| tmpfile.write("#{@resource[:name]} install\n") tmpfile.flush execute([:dpkg, "--set-selections"], :stdinfile => tmpfile.path.to_s) } end end end diff --git a/lib/puppet/provider/service/debian.rb b/lib/puppet/provider/service/debian.rb index e9ee7e92c..5903bd09f 100755 --- a/lib/puppet/provider/service/debian.rb +++ b/lib/puppet/provider/service/debian.rb @@ -1,68 +1,68 @@ # Manage debian services. Start/stop is the same as InitSvc, but enable/disable # is special. Puppet::Type.type(:service).provide :debian, :parent => :init do desc <<-EOT Debian's form of `init`-style management. The only differences from `init` are support for enabling and disabling services via `update-rc.d` and the ability to determine enabled status via `invoke-rc.d`. EOT commands :update_rc => "/usr/sbin/update-rc.d" # note this isn't being used as a command until # http://projects.reductivelabs.com/issues/2538 # is resolved. commands :invoke_rc => "/usr/sbin/invoke-rc.d" defaultfor :operatingsystem => [:debian, :ubuntu] def self.defpath superclass.defpath end # Remove the symlinks def disable if `dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?`.to_i == 0 update_rc @resource[:name], "disable" else update_rc "-f", @resource[:name], "remove" update_rc @resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", "." end end def enabled? - # TODO: Replace system call when Puppet::Util.execute gives us a way + # TODO: Replace system call when Puppet::Util::Execution.execute gives us a way # to determine exit status. http://projects.reductivelabs.com/issues/2538 system("/usr/sbin/invoke-rc.d", "--quiet", "--query", @resource[:name], "start") # 104 is the exit status when you query start an enabled service. # 106 is the exit status when the policy layer supplies a fallback action # See x-man-page://invoke-rc.d if [104, 106].include?($CHILD_STATUS.exitstatus) return :true elsif [105].include?($CHILD_STATUS.exitstatus) # 105 is unknown, which generally means the the iniscript does not support query # The debian policy states that the initscript should support methods of query # For those that do not, peform the checks manually # http://www.debian.org/doc/debian-policy/ch-opersys.html if get_start_link_count >= 4 return :true else return :false end else return :false end end def get_start_link_count Dir.glob("/etc/rc*.d/S*#{@resource[:name]}").length end def enable update_rc "-f", @resource[:name], "remove" update_rc @resource[:name], "defaults" end end diff --git a/lib/puppet/resource/type_collection.rb b/lib/puppet/resource/type_collection.rb index 89b0a16ed..082a11c1c 100644 --- a/lib/puppet/resource/type_collection.rb +++ b/lib/puppet/resource/type_collection.rb @@ -1,214 +1,214 @@ class Puppet::Resource::TypeCollection attr_reader :environment attr_accessor :parse_failed def clear @hostclasses.clear @definitions.clear @nodes.clear @watched_files.clear end def initialize(env) @environment = env.is_a?(String) ? Puppet::Node::Environment.new(env) : env @hostclasses = {} @definitions = {} @nodes = {} # So we can keep a list and match the first-defined regex @node_list = [] @watched_files = {} end def import_ast(ast, modname) ast.instantiate(modname).each do |instance| add(instance) end end def inspect "TypeCollection" + { :hostclasses => @hostclasses.keys, :definitions => @definitions.keys, :nodes => @nodes.keys }.inspect end def <<(thing) add(thing) self end def add(instance) if instance.type == :hostclass and other = @hostclasses[instance.name] and other.type == :hostclass other.merge(instance) return other end method = "add_#{instance.type}" send(method, instance) instance.resource_type_collection = self instance end def add_hostclass(instance) dupe_check(instance, @hostclasses) { |dupe| "Class '#{instance.name}' is already defined#{dupe.error_context}; cannot redefine" } dupe_check(instance, @definitions) { |dupe| "Definition '#{instance.name}' is already defined#{dupe.error_context}; cannot be redefined as a class" } @hostclasses[instance.name] = instance instance end def hostclass(name) @hostclasses[munge_name(name)] end def add_node(instance) dupe_check(instance, @nodes) { |dupe| "Node '#{instance.name}' is already defined#{dupe.error_context}; cannot redefine" } @node_list << instance @nodes[instance.name] = instance instance end def loader require 'puppet/parser/type_loader' @loader ||= Puppet::Parser::TypeLoader.new(environment) end def node(name) name = munge_name(name) if node = @nodes[name] return node end @node_list.each do |node| next unless node.name_is_regex? return node if node.match(name) end nil end def node_exists?(name) @nodes[munge_name(name)] end def nodes? @nodes.length > 0 end def add_definition(instance) dupe_check(instance, @hostclasses) { |dupe| "'#{instance.name}' is already defined#{dupe.error_context} as a class; cannot redefine as a definition" } dupe_check(instance, @definitions) { |dupe| "Definition '#{instance.name}' is already defined#{dupe.error_context}; cannot be redefined" } @definitions[instance.name] = instance end def definition(name) @definitions[munge_name(name)] end def find_node(namespaces, name) @nodes[munge_name(name)] end def find_hostclass(namespaces, name) find_or_load(namespaces, name, :hostclass) end def find_definition(namespaces, name) find_or_load(namespaces, name, :definition) end [:hostclasses, :nodes, :definitions].each do |m| define_method(m) do instance_variable_get("@#{m}").dup end end def require_reparse? @parse_failed || stale? end def stale? @watched_files.values.detect { |file| file.changed? } end def version return @version if defined?(@version) if environment[:config_version] == "" @version = Time.now.to_i return @version end - @version = Puppet::Util.execute([environment[:config_version]]).strip + @version = Puppet::Util::Execution.execute([environment[:config_version]]).strip rescue Puppet::ExecutionFailure => e raise Puppet::ParseError, "Unable to set config_version: #{e.message}" end def watch_file(file) @watched_files[file] = Puppet::Util::LoadedFile.new(file) end def watching_file?(file) @watched_files.include?(file) end private # Return a list of all possible fully-qualified names that might be # meant by the given name, in the context of namespaces. def resolve_namespaces(namespaces, name) name = name.downcase if name =~ /^::/ # name is explicitly fully qualified, so just return it, sans # initial "::". return [name.sub(/^::/, '')] end if name == "" # The name "" has special meaning--it always refers to a "main" # hostclass which contains all toplevel resources. return [""] end namespaces = [namespaces] unless namespaces.is_a?(Array) namespaces = namespaces.collect { |ns| ns.downcase } result = [] namespaces.each do |namespace| ary = namespace.split("::") # Search each namespace nesting in innermost-to-outermost order. while ary.length > 0 result << "#{ary.join("::")}::#{name}" ary.pop end # Finally, search the toplevel namespace. result << name end return result.uniq end # Resolve namespaces and find the given object. Autoload it if # necessary. def find_or_load(namespaces, name, type) resolve_namespaces(namespaces, name).each do |fqname| if result = send(type, fqname) || loader.try_load_fqname(type, fqname) return result end end # Nothing found. return nil end def munge_name(name) name.to_s.downcase end def dupe_check(instance, hash) return unless dupe = hash[instance.name] message = yield dupe instance.fail Puppet::ParseError, message end end diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb index b9e0483ff..d25e4d382 100644 --- a/lib/puppet/util.rb +++ b/lib/puppet/util.rb @@ -1,394 +1,427 @@ # A module to collect utility functions. require 'English' require 'puppet/util/monkey_patches' require 'sync' require 'tempfile' require 'puppet/external/lock' require 'puppet/error' require 'monitor' require 'puppet/util/execution_stub' require 'uri' module Puppet module Util require 'benchmark' # These are all for backward compatibility -- these are methods that used # to be in Puppet::Util but have been moved into external modules. require 'puppet/util/posix' extend Puppet::Util::POSIX @@sync_objects = {}.extend MonitorMixin def self.activerecord_version if (defined?(::ActiveRecord) and defined?(::ActiveRecord::VERSION) and defined?(::ActiveRecord::VERSION::MAJOR) and defined?(::ActiveRecord::VERSION::MINOR)) ([::ActiveRecord::VERSION::MAJOR, ::ActiveRecord::VERSION::MINOR].join('.').to_f) else 0 end end # Run some code with a specific environment. Resets the environment back to # what it was at the end of the code. def self.withenv(hash) saved = ENV.to_hash hash.each do |name, val| ENV[name.to_s] = val end yield ensure ENV.clear saved.each do |name, val| ENV[name] = val end end # Execute a given chunk of code with a new umask. def self.withumask(mask) cur = File.umask(mask) begin yield ensure File.umask(cur) end end def self.synchronize_on(x,type) sync_object,users = 0,1 begin @@sync_objects.synchronize { (@@sync_objects[x] ||= [Sync.new,0])[users] += 1 } @@sync_objects[x][sync_object].synchronize(type) { yield } ensure @@sync_objects.synchronize { @@sync_objects.delete(x) unless (@@sync_objects[x][users] -= 1) > 0 } end end # Change the process to a different user def self.chuser if group = Puppet[:group] begin Puppet::Util::SUIDManager.change_group(group, true) rescue => detail Puppet.warning "could not change to group #{group.inspect}: #{detail}" $stderr.puts "could not change to group #{group.inspect}" # Don't exit on failed group changes, since it's # not fatal #exit(74) end end if user = Puppet[:user] begin Puppet::Util::SUIDManager.change_user(user, true) rescue => detail $stderr.puts "Could not change to user #{user}: #{detail}" exit(74) end end end # Create instance methods for each of the log levels. This allows # the messages to be a little richer. Most classes will be calling this # method. def self.logmethods(klass, useself = true) Puppet::Util::Log.eachlevel { |level| klass.send(:define_method, level, proc { |args| args = args.join(" ") if args.is_a?(Array) if useself Puppet::Util::Log.create( :level => level, :source => self, :message => args ) else Puppet::Util::Log.create( :level => level, :message => args ) end }) } end # Proxy a bunch of methods to another object. def self.classproxy(klass, objmethod, *methods) classobj = class << klass; self; end methods.each do |method| classobj.send(:define_method, method) do |*args| obj = self.send(objmethod) obj.send(method, *args) end end end # Proxy a bunch of methods to another object. def self.proxy(klass, objmethod, *methods) methods.each do |method| klass.send(:define_method, method) do |*args| obj = self.send(objmethod) obj.send(method, *args) end end end def benchmark(*args) msg = args.pop level = args.pop object = nil if args.empty? if respond_to?(level) object = self else object = Puppet end else object = args.pop end raise Puppet::DevError, "Failed to provide level to :benchmark" unless level unless level == :none or object.respond_to? level raise Puppet::DevError, "Benchmarked object does not respond to #{level}" end # Only benchmark if our log level is high enough if level != :none and Puppet::Util::Log.sendlevel?(level) result = nil seconds = Benchmark.realtime { yield } object.send(level, msg + (" in %0.2f seconds" % seconds)) return seconds else yield end end def which(bin) if absolute_path?(bin) return bin if FileTest.file? bin and FileTest.executable? bin else ENV['PATH'].split(File::PATH_SEPARATOR).each do |dir| dest = File.expand_path(File.join(dir, bin)) if Puppet.features.microsoft_windows? && File.extname(dest).empty? exts = ENV['PATHEXT'] exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] exts.each do |ext| destext = File.expand_path(dest + ext) return destext if FileTest.file? destext and FileTest.executable? destext end end return dest if FileTest.file? dest and FileTest.executable? dest end end nil end module_function :which # Determine in a platform-specific way whether a path is absolute. This # defaults to the local platform if none is specified. def absolute_path?(path, platform=nil) # Escape once for the string literal, and once for the regex. slash = '[\\\\/]' name = '[^\\\\/]+' regexes = { :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, :posix => %r!^/!, } require 'puppet' platform ||= Puppet.features.microsoft_windows? ? :windows : :posix !! (path =~ regexes[platform]) end module_function :absolute_path? # Convert a path to a file URI def path_to_uri(path) return unless path params = { :scheme => 'file' } if Puppet.features.microsoft_windows? path = path.gsub(/\\/, '/') if unc = /^\/\/([^\/]+)(\/[^\/]+)/.match(path) params[:host] = unc[1] path = unc[2] elsif path =~ /^[a-z]:\//i path = '/' + path end end params[:path] = URI.escape(path) begin URI::Generic.build(params) rescue => detail raise Puppet::Error, "Failed to convert '#{path}' to URI: #{detail}" end end module_function :path_to_uri # Get the path component of a URI def uri_to_path(uri) return unless uri.is_a?(URI) path = URI.unescape(uri.path) if Puppet.features.microsoft_windows? and uri.scheme == 'file' if uri.host path = "//#{uri.host}" + path # UNC else path.sub!(/^\//, '') end end path end module_function :uri_to_path # Create an exclusive lock. def threadlock(resource, type = Sync::EX) Puppet::Util.synchronize_on(resource,type) { yield } end module_function :benchmark def memory unless defined?(@pmap) @pmap = which('pmap') end if @pmap %x{#{@pmap} #{Process.pid}| grep total}.chomp.sub(/^\s*total\s+/, '').sub(/K$/, '').to_i else 0 end end def symbolize(value) if value.respond_to? :intern value.intern else value end end def symbolizehash(hash) newhash = {} hash.each do |name, val| if name.is_a? String newhash[name.intern] = val else newhash[name] = val end end end def symbolizehash!(hash) hash.each do |name, val| if name.is_a? String hash[name.intern] = val hash.delete(name) end end hash end module_function :symbolize, :symbolizehash, :symbolizehash! # Just benchmark, with no logging. def thinmark seconds = Benchmark.realtime { yield } seconds end module_function :memory, :thinmark def secure_open(file,must_be_w,&block) raise Puppet::DevError,"secure_open only works with mode 'w'" unless must_be_w == 'w' raise Puppet::DevError,"secure_open only requires a block" unless block_given? Puppet.warning "#{file} was a symlink to #{File.readlink(file)}" if File.symlink?(file) if File.exists?(file) or File.symlink?(file) wait = File.symlink?(file) ? 5.0 : 0.1 File.delete(file) sleep wait # give it a chance to reappear, just in case someone is actively trying something. end begin File.open(file,File::CREAT|File::EXCL|File::TRUNC|File::WRONLY,&block) rescue Errno::EEXIST desc = File.symlink?(file) ? "symlink to #{File.readlink(file)}" : File.stat(file).ftype puts "Warning: #{file} was apparently created by another process (as" puts "a #{desc}) as soon as it was deleted by this process. Someone may be trying" puts "to do something objectionable (such as tricking you into overwriting system" puts "files if you are running as root)." raise end end module_function :secure_open + # Because IO#binread is only available in 1.9 def binread(file) File.open(file, 'rb') { |f| f.read } end module_function :binread + + # utility method to get the current call stack and format it to a human-readable string (which some IDEs/editors + # will recognize as links to the line numbers in the trace) + def self.pretty_backtrace() + caller(1).collect do |line| + file_path, line_num = line.split(":") + file_path = expand_symlinks(File.expand_path(file_path)) + + file_path + ":" + line_num + end .join("\n") + + end + + # utility method that takes a path as input, checks each component of the path to see if it is a symlink, and expands + # it if it is. returns the expanded path. + def self.expand_symlinks(file_path) + file_path.split("/").inject do |full_path, next_dir| + next_path = full_path + "/" + next_dir + if File.symlink?(next_path) then + link = File.readlink(next_path) + next_path = + case link + when /^\// then link + else + File.expand_path(full_path + "/" + link) + end + end + next_path + end + end + + + ####################################################################################################### # Deprecated methods relating to process execution; these have been moved to Puppet::Util::Execution ####################################################################################################### def execpipe(command, failonfail = true, &block) Puppet.deprecation_warning("Puppet::Util.execpipe is deprecated; please use Puppet::Util::Execution.execpipe") Puppet::Util::Execution.execpipe(command, failonfail, &block) end module_function :execpipe def execfail(command, exception) Puppet.deprecation_warning("Puppet::Util.execfail is deprecated; please use Puppet::Util::Execution.execfail") Puppet::Util::Execution.execfail(command, exception) end module_function :execfail def execute(command, arguments = {}) - Puppet.deprecation_warning("Puppet::Util.execute is deprecated; please use Puppet::Util::Execution.execute") + Puppet.deprecation_warning("Puppet::Util::Execution.execute is deprecated; please use Puppet::Util::Execution.execute") Puppet::Util::Execution.execute(command, arguments) end module_function :execute - end end require 'puppet/util/errors' require 'puppet/util/methodhelper' require 'puppet/util/metaid' require 'puppet/util/classgen' require 'puppet/util/docs' require 'puppet/util/execution' require 'puppet/util/logging' require 'puppet/util/package' require 'puppet/util/warnings' diff --git a/lib/puppet/util/execution_stub.rb b/lib/puppet/util/execution_stub.rb index af74e0f72..cc19e58a5 100644 --- a/lib/puppet/util/execution_stub.rb +++ b/lib/puppet/util/execution_stub.rb @@ -1,26 +1,26 @@ module Puppet::Util class ExecutionStub class << self - # Set a stub block that Puppet::Util.execute() should invoke instead + # Set a stub block that Puppet::Util::Execution.execute() should invoke instead # of actually executing commands on the target machine. Intended # for spec testing. # # The arguments passed to the block are |command, options|, where # command is an array of strings and options is an options hash. def set(&block) @value = block end # Uninstall any execution stub, so that calls to - # Puppet::Util.execute() behave normally again. + # Puppet::Util::Execution.execute() behave normally again. def reset @value = nil end # Retrieve the current execution stub, or nil if there is no stub. def current_value @value end end end end diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb index fdc6ccd9b..f4d878b65 100755 --- a/lib/puppet/util/filetype.rb +++ b/lib/puppet/util/filetype.rb @@ -1,283 +1,283 @@ # Basic classes for reading, writing, and emptying files. Not much # to see here. require 'puppet/util/selinux' require 'fileutils' class Puppet::Util::FileType attr_accessor :loaded, :path, :synced include Puppet::Util::SELinux class << self attr_accessor :name include Puppet::Util::ClassGen end # Create a new filetype. def self.newfiletype(name, &block) @filetypes ||= {} klass = genclass( name, :block => block, :prefix => "FileType", :hash => @filetypes ) # Rename the read and write methods, so that we're sure they # maintain the stats. klass.class_eval do # Rename the read method define_method(:real_read, instance_method(:read)) define_method(:read) do begin val = real_read @loaded = Time.now if val return val.gsub(/# HEADER.*\n/,'') else return "" end rescue Puppet::Error => detail raise rescue => detail puts detail.backtrace if Puppet[:trace] raise Puppet::Error, "#{self.class} could not read #{@path}: #{detail}" end end # And then the write method define_method(:real_write, instance_method(:write)) define_method(:write) do |text| begin val = real_write(text) @synced = Time.now return val rescue Puppet::Error => detail raise rescue => detail puts detail.backtrace if Puppet[:debug] raise Puppet::Error, "#{self.class} could not write #{@path}: #{detail}" end end end end def self.filetype(type) @filetypes[type] end # Pick or create a filebucket to use. def bucket @bucket ||= Puppet::Type.type(:filebucket).mkdefaultbucket.bucket end def initialize(path) raise ArgumentError.new("Path is nil") if path.nil? @path = path end # Operate on plain files. newfiletype(:flat) do # Back the file up before replacing it. def backup bucket.backup(@path) if File.exists?(@path) end # Read the file. def read if File.exist?(@path) File.read(@path) else return nil end end # Remove the file. def remove File.unlink(@path) if File.exist?(@path) end # Overwrite the file. def write(text) require "tempfile" tf = Tempfile.new("puppet") tf.print text; tf.flush FileUtils.cp(tf.path, @path) tf.close # If SELinux is present, we need to ensure the file has its expected context set_selinux_default_context(@path) end end # Operate on plain files. newfiletype(:ram) do @@tabs = {} def self.clear @@tabs.clear end def initialize(path) super @@tabs[@path] ||= "" end # Read the file. def read Puppet.info "Reading #{@path} from RAM" @@tabs[@path] end # Remove the file. def remove Puppet.info "Removing #{@path} from RAM" @@tabs[@path] = "" end # Overwrite the file. def write(text) Puppet.info "Writing #{@path} to RAM" @@tabs[@path] = text end end # Handle Linux-style cron tabs. newfiletype(:crontab) do def initialize(user) self.path = user end def path=(user) begin @uid = Puppet::Util.uid(user) rescue Puppet::Error => detail raise Puppet::Error, "Could not retrieve user #{user}" end # XXX We have to have the user name, not the uid, because some # systems *cough*linux*cough* require it that way @path = user end # Read a specific @path's cron tab. def read %x{#{cmdbase} -l 2>/dev/null} end # Remove a specific @path's cron tab. def remove if %w{Darwin FreeBSD}.include?(Facter.value("operatingsystem")) %x{/bin/echo yes | #{cmdbase} -r 2>/dev/null} else %x{#{cmdbase} -r 2>/dev/null} end end # Overwrite a specific @path's cron tab; must be passed the @path name # and the text with which to create the cron tab. def write(text) IO.popen("#{cmdbase()} -", "w") { |p| p.print text } end private # Only add the -u flag when the @path is different. Fedora apparently # does not think I should be allowed to set the @path to my own user name def cmdbase cmd = nil if @uid == Puppet::Util::SUIDManager.uid || Facter.value(:operatingsystem) == "HP-UX" return "crontab" else return "crontab -u #{@path}" end end end # SunOS has completely different cron commands; this class implements # its versions. newfiletype(:suntab) do # Read a specific @path's cron tab. def read - output = Puppet::Util.execute(%w{crontab -l}, :uid => @path) + output = Puppet::Util::Execution.execute(%w{crontab -l}, :uid => @path) return "" if output.include?("can't open your crontab") raise Puppet::Error, "User #{@path} not authorized to use cron" if output.include?("you are not authorized to use cron") return output rescue => detail raise Puppet::Error, "Could not read crontab for #{@path}: #{detail}" end # Remove a specific @path's cron tab. def remove - Puppet::Util.execute(%w{crontab -r}, :uid => @path) + Puppet::Util::Execution.execute(%w{crontab -r}, :uid => @path) rescue => detail raise Puppet::Error, "Could not remove crontab for #{@path}: #{detail}" end # Overwrite a specific @path's cron tab; must be passed the @path name # and the text with which to create the cron tab. def write(text) puts text require "tempfile" output_file = Tempfile.new("puppet") fh = output_file.open fh.print text fh.close # We have to chown the stupid file to the user. File.chown(Puppet::Util.uid(@path), nil, output_file.path) begin - Puppet::Util.execute(["crontab", output_file.path], :uid => @path) + Puppet::Util::Execution.execute(["crontab", output_file.path], :uid => @path) rescue => detail raise Puppet::Error, "Could not write crontab for #{@path}: #{detail}" end output_file.delete end end # Support for AIX crontab with output different than suntab's crontab command. newfiletype(:aixtab) do # Read a specific @path's cron tab. def read - output = Puppet::Util.execute(%w{crontab -l}, :uid => @path) + output = Puppet::Util::Execution.execute(%w{crontab -l}, :uid => @path) raise Puppet::Error, "User #{@path} not authorized to use cron" if output.include?("You are not authorized to use the cron command") return output rescue => detail raise Puppet::Error, "Could not read crontab for #{@path}: #{detail}" end # Remove a specific @path's cron tab. def remove - Puppet::Util.execute(%w{crontab -r}, :uid => @path) + Puppet::Util::Execution.execute(%w{crontab -r}, :uid => @path) rescue => detail raise Puppet::Error, "Could not remove crontab for #{@path}: #{detail}" end # Overwrite a specific @path's cron tab; must be passed the @path name # and the text with which to create the cron tab. def write(text) require "tempfile" output_file = Tempfile.new("puppet") fh = output_file.open fh.print text fh.close # We have to chown the stupid file to the user. File.chown(Puppet::Util.uid(@path), nil, output_file.path) begin - Puppet::Util.execute(["crontab", output_file.path], :uid => @path) + Puppet::Util::Execution.execute(["crontab", output_file.path], :uid => @path) rescue => detail raise Puppet::Error, "Could not write crontab for #{@path}: #{detail}" ensure output_file.delete end end end end diff --git a/lib/puppet/util/logging.rb b/lib/puppet/util/logging.rb index 4e76ae414..eea321bd9 100644 --- a/lib/puppet/util/logging.rb +++ b/lib/puppet/util/logging.rb @@ -1,51 +1,127 @@ # A module to make logging a bit easier. require 'puppet/util/log' module Puppet::Util::Logging def send_log(level, message) Puppet::Util::Log.create({:level => level, :source => log_source, :message => message}.merge(log_metadata)) end # Create a method for each log level. Puppet::Util::Log.eachlevel do |level| define_method(level) do |args| args = args.join(" ") if args.is_a?(Array) send_log(level, args) end end + # Log an exception. Will also log the backtrace if Puppet[:trace] is set. + # Parameters: + # [exception] an Exception to log + # [options] an optional Hash containing some configuration options about how to log the exception. + # Available options include: + # * :message => an optional String overriding the message to be logged; by default, we log Exception.message. + # If you pass a String here, your string will be logged instead. You may also pass nil if you don't + # wish to log a message at all; in this case it is likely that you are only calling this method in order + # to take advantage of the backtrace logging. + # * :level => the log level to log the message at. Defaults to :err. + def log_exception(exception, options = {}) + + default_options = { + :message => :default, + :level => :err, + } + + options = default_options.merge(options) + + case options[:message] + when :default + send(options[:level], exception.message) + when nil + # don't log anything if they passed a nil; they are just calling for the optional backtrace logging + else + send(options[:level], options[:message]) + end + + send(options[:level], exception.backtrace) if Puppet[:trace] && exception.backtrace + end + + class DeprecationWarning < Exception; end + def deprecation_warning(message) - $deprecation_warnings ||= Hash.new(0) - if $deprecation_warnings.length < 100 and ($deprecation_warnings[message] += 1) == 1 - warning message + $deprecation_warnings ||= {} + if $deprecation_warnings.length < 100 then + offender = get_deprecation_offender() + if (! $deprecation_warnings.has_key?(offender)) then + $deprecation_warnings[offender] = message + log_exception(DeprecationWarning.new(message), :level => :warning) + end end end + def get_deprecation_offender() + # we have to put this in its own method to simplify testing; we need to be able to mock the offender results in + # order to test this class, and our framework does not appear to enjoy it if you try to mock Kernel.caller + # + # let's find the offending line; we need to jump back up the stack a few steps to find the method that called + # the deprecated method + caller()[2] + end + def clear_deprecation_warnings $deprecation_warnings.clear if $deprecation_warnings end + # utility method that can be called, e.g., from spec_helper config.after, when tracking down calls to deprecated + # code. + # Parameters: + # [deprecations_file] relative or absolute path of a file to log the deprecations to + # [pattern] (default nil) if specified, will only log deprecations whose message matches the provided pattern + def log_deprecations_to_file(deprecations_file, pattern = nil) + # this method may get called lots and lots of times (e.g., from spec_helper config.after) without the global + # list of deprecation warnings being cleared out. We don't want to keep logging the same offenders over and over, + # so, we need to keep track of what we've logged. + # + # It'd be nice if we could just clear out the list of deprecation warnings, but then the very next spec might + # find the same offender, and we'd end up logging it again. + $logged_deprecation_warnings ||= {} + + File.open(deprecations_file, "a") do |f| + if ($deprecation_warnings) then + $deprecation_warnings.each do |offender, message| + if (! $logged_deprecation_warnings.has_key?(offender)) then + $logged_deprecation_warnings[offender] = true + if ((pattern.nil?) || (message =~ pattern)) then + f.puts(message) + f.puts(offender) + f.puts() + end + end + end + end + end + end + private def is_resource? defined?(Puppet::Type) && is_a?(Puppet::Type) end def is_resource_parameter? defined?(Puppet::Parameter) && is_a?(Puppet::Parameter) end def log_metadata [:file, :line, :tags].inject({}) do |result, attr| result[attr] = send(attr) if respond_to?(attr) result end end def log_source # We need to guard the existence of the constants, since this module is used by the base Puppet module. (is_resource? or is_resource_parameter?) and respond_to?(:path) and return path.to_s to_s end end diff --git a/lib/puppet/util/suidmanager.rb b/lib/puppet/util/suidmanager.rb index dd21f85d5..65e0822f9 100644 --- a/lib/puppet/util/suidmanager.rb +++ b/lib/puppet/util/suidmanager.rb @@ -1,193 +1,193 @@ require 'puppet/util/warnings' require 'forwardable' module Puppet::Util::SUIDManager include Puppet::Util::Warnings extend Forwardable # Note groups= is handled specially due to a bug in OS X 10.6, 10.7, # and probably upcoming releases... to_delegate_to_process = [ :euid=, :euid, :egid=, :egid, :uid=, :uid, :gid=, :gid, :groups ] to_delegate_to_process.each do |method| def_delegator Process, method module_function method end def osx_maj_ver return @osx_maj_ver unless @osx_maj_ver.nil? require 'facter' # 'kernel' is available without explicitly loading all facts if Facter.value('kernel') != 'Darwin' @osx_maj_ver = false return @osx_maj_ver end # But 'macosx_productversion_major' requires it. Facter.loadfacts @osx_maj_ver = Facter.value('macosx_productversion_major') end module_function :osx_maj_ver def groups=(grouplist) begin return Process.groups = grouplist rescue Errno::EINVAL => e #We catch Errno::EINVAL as some operating systems (OS X in particular) can # cause troubles when using Process#groups= to change *this* user / process # list of supplementary groups membership. This is done via Ruby's function # "static VALUE proc_setgroups(VALUE obj, VALUE ary)" which is effectively # a wrapper for "int setgroups(size_t size, const gid_t *list)" (part of SVr4 # and 4.3BSD but not in POSIX.1-2001) that fails and sets errno to EINVAL. # # This does not appear to be a problem with Ruby but rather an issue on the # operating system side. Therefore we catch the exception and look whether # we run under OS X or not -- if so, then we acknowledge the problem and # re-throw the exception otherwise. if osx_maj_ver and not osx_maj_ver.empty? return true else raise e end end end module_function :groups= def self.root? return Process.uid == 0 unless Puppet.features.microsoft_windows? require 'sys/admin' require 'win32/security' require 'facter' majversion = Facter.value(:kernelmajversion) return false unless majversion # if Vista or later, check for unrestricted process token return Win32::Security.elevated_security? unless majversion.to_f < 6.0 group = Sys::Admin.get_group("Administrators", :sid => Win32::Security::SID::BuiltinAdministrators) group and group.members.index(Sys::Admin.get_login) != nil end # Runs block setting uid and gid if provided then restoring original ids def asuser(new_uid=nil, new_gid=nil) return yield if Puppet.features.microsoft_windows? or !root? old_euid, old_egid = self.euid, self.egid begin change_group(new_gid) if new_gid change_user(new_uid) if new_uid yield ensure change_group(old_egid) change_user(old_euid) end end module_function :asuser def change_group(group, permanently=false) gid = convert_xid(:gid, group) raise Puppet::Error, "No such group #{group}" unless gid if permanently begin Process::GID.change_privilege(gid) rescue NotImplementedError Process.egid = gid Process.gid = gid end else Process.egid = gid end end module_function :change_group def change_user(user, permanently=false) uid = convert_xid(:uid, user) raise Puppet::Error, "No such user #{user}" unless uid if permanently begin Process::UID.change_privilege(uid) rescue NotImplementedError # If changing uid, we must be root. So initgroups first here. initgroups(uid) Process.euid = uid Process.uid = uid end else # If we're already root, initgroups before changing euid. If we're not, # change euid (to root) first. if Process.euid == 0 initgroups(uid) Process.euid = uid else Process.euid = uid initgroups(uid) end end end module_function :change_user # Make sure the passed argument is a number. def convert_xid(type, id) map = {:gid => :group, :uid => :user} raise ArgumentError, "Invalid id type #{type}" unless map.include?(type) ret = Puppet::Util.send(type, id) if ret == nil raise Puppet::Error, "Invalid #{map[type]}: #{id}" end ret end module_function :convert_xid # Initialize supplementary groups def initgroups(user) require 'etc' Process.initgroups(Etc.getpwuid(user).name, Process.gid) end module_function :initgroups # Run a command and capture the output # Parameters: # [command] the command to execute # [new_uid] (optional) a userid to run the command as # [new_gid] (optional) a groupid to run the command as # [options] (optional, defaults to {}) a hash of option key/value pairs; currently supported: # :override_locale (defaults to true) a flag indicating whether or puppet should temporarily override the # system locale for the duration of the command. If true, the locale will be set to 'C' to ensure consistent # output / formatting from the command, which makes it much easier to parse the output. If false, the system # locale will be respected. # :custom_environment (default {}) -- a hash of key/value pairs to set as environment variables for the duration # of the command def run_and_capture(command, new_uid=nil, new_gid=nil, options = {}) # specifying these here rather than in the method signature to allow callers to pass in a partial # set of overrides without affecting the default values for options that they don't pass in default_options = { :override_locale => true, :custom_environment => {}, } options = default_options.merge(options) - output = Puppet::Util.execute(command, :failonfail => false, :combine => true, + output = Puppet::Util::Execution.execute(command, :failonfail => false, :combine => true, :uid => new_uid, :gid => new_gid, :override_locale => options[:override_locale], :custom_environment => options[:custom_environment]) [output, $CHILD_STATUS.dup] end module_function :run_and_capture def system(command, new_uid=nil, new_gid=nil) status = nil asuser(new_uid, new_gid) do Kernel.system(command) status = $CHILD_STATUS.dup end status end module_function :system end diff --git a/spec/integration/util_spec.rb b/spec/integration/util_spec.rb index 2b596fd7c..69f707cbf 100755 --- a/spec/integration/util_spec.rb +++ b/spec/integration/util_spec.rb @@ -1,29 +1,29 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Util do describe "#execute" do it "should properly allow stdout and stderr to share a file" do command = "ruby -e '(1..10).each {|i| (i%2==0) ? $stdout.puts(i) : $stderr.puts(i)}'" - Puppet::Util.execute(command, :combine => true).split.should =~ [*'1'..'10'] + Puppet::Util::Execution.execute(command, :combine => true).split.should =~ [*'1'..'10'] end it "should return output and set $CHILD_STATUS" do command = "ruby -e 'puts \"foo\"; exit 42'" - output = Puppet::Util.execute(command, {:failonfail => false}) + output = Puppet::Util::Execution.execute(command, {:failonfail => false}) output.should == "foo\n" $CHILD_STATUS.exitstatus.should == 42 end it "should raise an error if non-zero exit status is returned" do command = "ruby -e 'exit 43'" - expect { Puppet::Util.execute(command) }.to raise_error(Puppet::ExecutionFailure, /Execution of '#{command}' returned 43: /) + expect { Puppet::Util::Execution.execute(command) }.to raise_error(Puppet::ExecutionFailure, /Execution of '#{command}' returned 43: /) $CHILD_STATUS.exitstatus.should == 43 end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c842dc500..a1196f132 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,126 +1,129 @@ dir = File.expand_path(File.dirname(__FILE__)) $LOAD_PATH.unshift File.join(dir, 'lib') # Don't want puppet getting the command line arguments for rake or autotest ARGV.clear require 'puppet' require 'mocha' gem 'rspec', '>=2.0.0' require 'rspec/expectations' # So everyone else doesn't have to include this base constant. module PuppetSpec FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR) end require 'pathname' require 'tmpdir' require 'puppet_spec/verbose' require 'puppet_spec/files' require 'puppet_spec/fixtures' require 'puppet_spec/matchers' require 'monkey_patches/alias_should_to_must' require 'monkey_patches/publicize_methods' Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour| require behaviour.relative_path_from(Pathname.new(dir)) end RSpec.configure do |config| include PuppetSpec::Fixtures config.mock_with :mocha config.before :each do # Disabling garbage collection inside each test, and only running it at # the end of each block, gives us an ~ 15 percent speedup, and more on # some platforms *cough* windows *cough* that are a little slower. GC.disable # We need to preserve the current state of all our indirection cache and # terminus classes. This is pretty important, because changes to these # are global and lead to order dependencies in our testing. # # We go direct to the implementation because there is no safe, sane public # API to manage restoration of these to their default values. This # should, once the value is proved, be moved to a standard API on the # indirector. # # To make things worse, a number of the tests stub parts of the # indirector. These stubs have very specific expectations that what # little of the public API we could use is, well, likely to explode # randomly in some tests. So, direct access. --daniel 2011-08-30 $saved_indirection_state = {} indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections) indirections.each do |indirector| $saved_indirection_state[indirector.name] = { :@terminus_class => indirector.instance_variable_get(:@terminus_class), :@cache_class => indirector.instance_variable_get(:@cache_class) } end # these globals are set by Application $puppet_application_mode = nil $puppet_application_name = nil # REVISIT: I think this conceals other bad tests, but I don't have time to # fully diagnose those right now. When you read this, please come tell me # I suck for letting this float. --daniel 2011-04-21 Signal.stubs(:trap) # Set the confdir and vardir to gibberish so that tests # have to be correctly mocked. Puppet[:confdir] = "/dev/null" Puppet[:vardir] = "/dev/null" # Avoid opening ports to the outside world Puppet.settings[:bindaddress] = "127.0.0.1" # We don't want to depend upon the reported domain name of the # machine running the tests, nor upon the DNS setup of that # domain. Puppet.settings[:use_srv_records] = false @logs = [] Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(@logs)) @log_level = Puppet::Util::Log.level end config.after :each do Puppet.settings.clear Puppet::Node::Environment.clear Puppet::Util::Storage.clear Puppet::Util::ExecutionStub.reset PuppetSpec::Files.cleanup @logs.clear Puppet::Util::Log.close_all Puppet::Util::Log.level = @log_level + # uncommenting and manipulating this can be useful when tracking down calls to deprecated code + #Puppet.log_deprecations_to_file("deprecations.txt", /^Puppet::Util.exec/) + # Restore the indirector configuration. See before hook. indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections) indirections.each do |indirector| $saved_indirection_state.fetch(indirector.name, {}).each do |variable, value| indirector.instance_variable_set(variable, value) end end $saved_indirection_state = {} # Some tests can cause us to connect, in which case the lingering # connection is a resource that can cause unexpected failure in later # tests, as well as sharing state accidentally. # We're testing if ActiveRecord::Base is defined because some test cases # may stub Puppet.features.rails? which is how we should normally # introspect for this functionality. ActiveRecord::Base.remove_connection if defined?(ActiveRecord::Base) # This will perform a GC between tests, but only if actually required. We # experimented with forcing a GC run, and that was less efficient than # just letting it run all the time. GC.enable end end diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb index e64146056..18cf870d5 100755 --- a/spec/unit/provider/package/dpkg_spec.rb +++ b/spec/unit/provider/package/dpkg_spec.rb @@ -1,224 +1,224 @@ #!/usr/bin/env rspec require 'spec_helper' provider = Puppet::Type.type(:package).provider(:dpkg) describe provider do before do @resource = stub 'resource', :[] => "asdf" @provider = provider.new(@resource) @provider.expects(:execute).never # forbid "manual" executions @fakeresult = "install ok installed asdf 1.0\n" end it "should have documentation" do provider.doc.should be_instance_of(String) end describe "when listing all instances" do before do provider.stubs(:command).with(:dpkgquery).returns "myquery" end it "should use dpkg-query" do provider.expects(:command).with(:dpkgquery).returns "myquery" - provider.expects(:execpipe).with("myquery -W --showformat '${Status} ${Package} ${Version}\\n'").returns @fakeresult + Puppet::Util::Execution.expects(:execpipe).with("myquery -W --showformat '${Status} ${Package} ${Version}\\n'").returns @fakeresult provider.instances end it "should create and return an instance with each parsed line from dpkg-query" do pipe = mock 'pipe' pipe.expects(:each).yields @fakeresult - provider.expects(:execpipe).yields pipe + Puppet::Util::Execution.expects(:execpipe).yields pipe asdf = mock 'pkg1' provider.expects(:new).with(:ensure => "1.0", :error => "ok", :desired => "install", :name => "asdf", :status => "installed", :provider => :dpkg).returns asdf provider.instances.should == [asdf] end it "should warn on and ignore any lines it does not understand" do pipe = mock 'pipe' pipe.expects(:each).yields "foobar" - provider.expects(:execpipe).yields pipe + Puppet::Util::Execution.expects(:execpipe).yields pipe Puppet.expects(:warning) provider.expects(:new).never provider.instances.should == [] end end describe "when querying the current state" do it "should use dpkg-query" do @provider.expects(:dpkgquery).with("-W", "--showformat",'${Status} ${Package} ${Version}\\n', "asdf").returns @fakeresult @provider.query end it "should consider the package purged if dpkg-query fails" do @provider.expects(:dpkgquery).raises Puppet::ExecutionFailure.new("eh") @provider.query[:ensure].should == :purged end it "should return a hash of the found status with the desired state, error state, status, name, and 'ensure'" do @provider.expects(:dpkgquery).returns @fakeresult @provider.query.should == {:ensure => "1.0", :error => "ok", :desired => "install", :name => "asdf", :status => "installed", :provider => :dpkg} end it "should consider the package absent if the dpkg-query result cannot be interpreted" do @provider.expects(:dpkgquery).returns "somebaddata" @provider.query[:ensure].should == :absent end it "should fail if an error is discovered" do @provider.expects(:dpkgquery).returns @fakeresult.sub("ok", "error") lambda { @provider.query }.should raise_error(Puppet::Error) end it "should consider the package purged if it is marked 'not-installed'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "not-installed") @provider.query[:ensure].should == :purged end it "should consider the package absent if it is marked 'config-files'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "config-files") @provider.query[:ensure].should == :absent end it "should consider the package absent if it is marked 'half-installed'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "half-installed") @provider.query[:ensure].should == :absent end it "should consider the package absent if it is marked 'unpacked'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "unpacked") @provider.query[:ensure].should == :absent end it "should consider the package absent if it is marked 'half-configured'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "half-configured") @provider.query[:ensure].should == :absent end it "should consider the package held if its state is 'hold'" do @provider.expects(:dpkgquery).returns @fakeresult.sub("install", "hold") @provider.query[:ensure].should == :held end end it "should be able to install" do @provider.should respond_to(:install) end describe "when installing" do before do @resource.stubs(:[]).with(:source).returns "mypkg" end it "should fail to install if no source is specified in the resource" do @resource.expects(:[]).with(:source).returns nil lambda { @provider.install }.should raise_error(ArgumentError) end it "should use 'dpkg -i' to install the package" do @resource.expects(:[]).with(:source).returns "mypackagefile" @provider.expects(:unhold) @provider.expects(:dpkg).with { |*command| command[-1] == "mypackagefile" and command[-2] == "-i" } @provider.install end it "should keep old config files if told to do so" do @resource.expects(:[]).with(:configfiles).returns :keep @provider.expects(:unhold) @provider.expects(:dpkg).with { |*command| command[0] == "--force-confold" } @provider.install end it "should replace old config files if told to do so" do @resource.expects(:[]).with(:configfiles).returns :replace @provider.expects(:unhold) @provider.expects(:dpkg).with { |*command| command[0] == "--force-confnew" } @provider.install end it "should ensure any hold is removed" do @provider.expects(:unhold).once @provider.expects(:dpkg) @provider.install end end describe "when holding or unholding" do before do @tempfile = stub 'tempfile', :print => nil, :close => nil, :flush => nil, :path => "/other/file" @tempfile.stubs(:write) Tempfile.stubs(:new).returns @tempfile end it "should install first if holding" do @provider.stubs(:execute) @provider.expects(:install).once @provider.hold end it "should execute dpkg --set-selections when holding" do @provider.stubs(:install) @provider.expects(:execute).with([:dpkg, '--set-selections'], {:stdinfile => @tempfile.path}).once @provider.hold end it "should execute dpkg --set-selections when unholding" do @provider.stubs(:install) @provider.expects(:execute).with([:dpkg, '--set-selections'], {:stdinfile => @tempfile.path}).once @provider.hold end end it "should use :install to update" do @provider.expects(:install) @provider.update end describe "when determining latest available version" do it "should return the version found by dpkg-deb" do @resource.expects(:[]).with(:source).returns "myfile" @provider.expects(:dpkg_deb).with { |*command| command[-1] == "myfile" }.returns "asdf\t1.0" @provider.latest.should == "1.0" end it "should warn if the package file contains a different package" do @provider.expects(:dpkg_deb).returns("foo\tversion") @provider.expects(:warning) @provider.latest end it "should cope with names containing ++" do @resource = stub 'resource', :[] => "asdf++" @provider = provider.new(@resource) @provider.expects(:dpkg_deb).returns "asdf++\t1.0" @provider.latest.should == "1.0" end end it "should use 'dpkg -r' to uninstall" do @provider.expects(:dpkg).with("-r", "asdf") @provider.uninstall end it "should use 'dpkg --purge' to purge" do @provider.expects(:dpkg).with("--purge", "asdf") @provider.purge end end diff --git a/spec/unit/resource/type_collection_spec.rb b/spec/unit/resource/type_collection_spec.rb index 0c997c2ad..23590ac64 100755 --- a/spec/unit/resource/type_collection_spec.rb +++ b/spec/unit/resource/type_collection_spec.rb @@ -1,451 +1,451 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/resource/type_collection' require 'puppet/resource/type' describe Puppet::Resource::TypeCollection do include PuppetSpec::Files before do @instance = Puppet::Resource::Type.new(:hostclass, "foo") @code = Puppet::Resource::TypeCollection.new("env") end it "should require an environment at initialization" do env = Puppet::Node::Environment.new("testing") Puppet::Resource::TypeCollection.new(env).environment.should equal(env) end it "should convert the environment into an environment instance if a string is provided" do env = Puppet::Node::Environment.new("testing") Puppet::Resource::TypeCollection.new("testing").environment.should equal(env) end it "should create a 'loader' at initialization" do Puppet::Resource::TypeCollection.new("testing").loader.should be_instance_of(Puppet::Parser::TypeLoader) end it "should be able to add a resource type" do Puppet::Resource::TypeCollection.new("env").should respond_to(:add) end it "should consider '<<' to be an alias to 'add' but should return self" do loader = Puppet::Resource::TypeCollection.new("env") loader.expects(:add).with "foo" loader.expects(:add).with "bar" loader << "foo" << "bar" end it "should set itself as the code collection for added resource types" do loader = Puppet::Resource::TypeCollection.new("env") node = Puppet::Resource::Type.new(:node, "foo") @code.add(node) @code.node("foo").should equal(node) node.resource_type_collection.should equal(@code) end it "should store node resource types as nodes" do node = Puppet::Resource::Type.new(:node, "foo") @code.add(node) @code.node("foo").should equal(node) end it "should store hostclasses as hostclasses" do klass = Puppet::Resource::Type.new(:hostclass, "foo") @code.add(klass) @code.hostclass("foo").should equal(klass) end it "should store definitions as definitions" do define = Puppet::Resource::Type.new(:definition, "foo") @code.add(define) @code.definition("foo").should equal(define) end it "should merge new classes with existing classes of the same name" do loader = Puppet::Resource::TypeCollection.new("env") first = Puppet::Resource::Type.new(:hostclass, "foo") second = Puppet::Resource::Type.new(:hostclass, "foo") loader.add first first.expects(:merge).with(second) loader.add(second) end it "should remove all nodes, classes, and definitions when cleared" do loader = Puppet::Resource::TypeCollection.new("env") loader.add Puppet::Resource::Type.new(:hostclass, "class") loader.add Puppet::Resource::Type.new(:definition, "define") loader.add Puppet::Resource::Type.new(:node, "node") watched_file = tmpfile('watched_file') loader.watch_file(watched_file) loader.clear loader.hostclass("class").should be_nil loader.definition("define").should be_nil loader.node("node").should be_nil loader.should_not be_watching_file(watched_file) end describe "when resolving namespaces" do [ ['', '::foo', ['foo']], ['a', '::foo', ['foo']], ['a::b', '::foo', ['foo']], [['a::b'], '::foo', ['foo']], [['a::b', 'c'], '::foo', ['foo']], [['A::B', 'C'], '::Foo', ['foo']], ['', '', ['']], ['a', '', ['']], ['a::b', '', ['']], [['a::b'], '', ['']], [['a::b', 'c'], '', ['']], [['A::B', 'C'], '', ['']], ['', 'foo', ['foo']], ['a', 'foo', ['a::foo', 'foo']], ['a::b', 'foo', ['a::b::foo', 'a::foo', 'foo']], ['A::B', 'Foo', ['a::b::foo', 'a::foo', 'foo']], [['a::b'], 'foo', ['a::b::foo', 'a::foo', 'foo']], [['a', 'b'], 'foo', ['a::foo', 'foo', 'b::foo']], [['a::b', 'c::d'], 'foo', ['a::b::foo', 'a::foo', 'foo', 'c::d::foo', 'c::foo']], [['a::b', 'a::c'], 'foo', ['a::b::foo', 'a::foo', 'foo', 'a::c::foo']], ].each do |namespaces, name, expected_result| it "should resolve #{name.inspect} in namespaces #{namespaces.inspect} correctly" do @code.instance_eval { resolve_namespaces(namespaces, name) }.should == expected_result end end end describe "when looking up names" do before do @type = Puppet::Resource::Type.new(:hostclass, "ns::klass") end it "should support looking up with multiple namespaces" do @code.add @type @code.find_hostclass(%w{boo baz ns}, "klass").should equal(@type) end it "should not attempt to import anything when the type is already defined" do @code.add @type @code.loader.expects(:import).never @code.find_hostclass(%w{ns}, "klass").should equal(@type) end describe "that need to be loaded" do it "should use the loader to load the files" do @code.loader.expects(:try_load_fqname).with(:hostclass, "ns::klass") @code.loader.expects(:try_load_fqname).with(:hostclass, "klass") @code.find_hostclass(["ns"], "klass") end it "should downcase the name and downcase and array-fy the namespaces before passing to the loader" do @code.loader.expects(:try_load_fqname).with(:hostclass, "ns::klass") @code.loader.expects(:try_load_fqname).with(:hostclass, "klass") @code.find_hostclass("Ns", "Klass") end it "should use the class returned by the loader" do @code.loader.expects(:try_load_fqname).returns(:klass) @code.expects(:hostclass).with("ns::klass").returns(false) @code.find_hostclass("ns", "klass").should == :klass end it "should return nil if the name isn't found" do @code.stubs(:try_load_fqname).returns(nil) @code.find_hostclass("Ns", "Klass").should be_nil end it "already-loaded names at broader scopes should not shadow autoloaded names" do @code.add Puppet::Resource::Type.new(:hostclass, "bar") @code.loader.expects(:try_load_fqname).with(:hostclass, "foo::bar").returns(:foobar) @code.find_hostclass("foo", "bar").should == :foobar end end end %w{hostclass node definition}.each do |data| before do @instance = Puppet::Resource::Type.new(data, "foo") end it "should have a method for adding a #{data}" do Puppet::Resource::TypeCollection.new("env").should respond_to("add_#{data}") end it "should use the name of the instance to add it" do loader = Puppet::Resource::TypeCollection.new("env") loader.send("add_#{data}", @instance) loader.send(data, @instance.name).should equal(@instance) end unless data == "hostclass" it "should fail to add a #{data} when one already exists" do loader = Puppet::Resource::TypeCollection.new("env") loader.add @instance lambda { loader.add(@instance) }.should raise_error(Puppet::ParseError) end end it "should return the added #{data}" do loader = Puppet::Resource::TypeCollection.new("env") loader.add(@instance).should equal(@instance) end it "should be able to retrieve #{data} by name" do loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(data, "bar") loader.add instance loader.send(data, "bar").should equal(instance) end it "should retrieve #{data} insensitive to case" do loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(data, "Bar") loader.add instance loader.send(data, "bAr").should equal(instance) end it "should return nil when asked for a #{data} that has not been added" do Puppet::Resource::TypeCollection.new("env").send(data, "foo").should be_nil end it "should be able to retrieve all #{data}s" do plurals = { "hostclass" => "hostclasses", "node" => "nodes", "definition" => "definitions" } loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(data, "foo") loader.add instance loader.send(plurals[data]).should == { "foo" => instance } end end describe "when finding a qualified instance" do it "should return any found instance if the instance name is fully qualified" do loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(:hostclass, "foo::bar") loader.add instance loader.find_hostclass("namespace", "::foo::bar").should equal(instance) end it "should return nil if the instance name is fully qualified and no such instance exists" do loader = Puppet::Resource::TypeCollection.new("env") loader.find_hostclass("namespace", "::foo::bar").should be_nil end it "should be able to find classes in the base namespace" do loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(:hostclass, "foo") loader.add instance loader.find_hostclass("", "foo").should equal(instance) end it "should return the partially qualified object if it exists in a provided namespace" do loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz") loader.add instance loader.find_hostclass("foo", "bar::baz").should equal(instance) end it "should be able to find partially qualified objects in any of the provided namespaces" do loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz") loader.add instance loader.find_hostclass(["nons", "foo", "otherns"], "bar::baz").should equal(instance) end it "should return the unqualified object if it exists in a provided namespace" do loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(:hostclass, "foo::bar") loader.add instance loader.find_hostclass("foo", "bar").should equal(instance) end it "should return the unqualified object if it exists in the parent namespace" do loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(:hostclass, "foo::bar") loader.add instance loader.find_hostclass("foo::bar::baz", "bar").should equal(instance) end it "should should return the partially qualified object if it exists in the parent namespace" do loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz") loader.add instance loader.find_hostclass("foo::bar", "bar::baz").should equal(instance) end it "should return the qualified object if it exists in the root namespace" do loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz") loader.add instance loader.find_hostclass("foo::bar", "foo::bar::baz").should equal(instance) end it "should return nil if the object cannot be found" do loader = Puppet::Resource::TypeCollection.new("env") instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz") loader.add instance loader.find_hostclass("foo::bar", "eh").should be_nil end describe "when topscope has a class that has the same name as a local class" do before do @loader = Puppet::Resource::TypeCollection.new("env") [ "foo::bar", "bar" ].each do |name| @loader.add Puppet::Resource::Type.new(:hostclass, name) end end it "should favor the local class, if the name is unqualified" do @loader.find_hostclass("foo", "bar").name.should == 'foo::bar' end it "should only look in the topclass, if the name is qualified" do @loader.find_hostclass("foo", "::bar").name.should == 'bar' end end it "should not look in the local scope for classes when the name is qualified" do @loader = Puppet::Resource::TypeCollection.new("env") @loader.add Puppet::Resource::Type.new(:hostclass, "foo::bar") @loader.find_hostclass("foo", "::bar").should == nil end end it "should be able to find nodes" do node = Puppet::Resource::Type.new(:node, "bar") loader = Puppet::Resource::TypeCollection.new("env") loader.add(node) loader.find_node(stub("ignored"), "bar").should == node end it "should use the 'find_or_load' method to find hostclasses" do loader = Puppet::Resource::TypeCollection.new("env") loader.expects(:find_or_load).with("foo", "bar", :hostclass) loader.find_hostclass("foo", "bar") end it "should use the 'find_or_load' method to find definitions" do loader = Puppet::Resource::TypeCollection.new("env") loader.expects(:find_or_load).with("foo", "bar", :definition) loader.find_definition("foo", "bar") end it "should indicate whether any nodes are defined" do loader = Puppet::Resource::TypeCollection.new("env") loader.add_node(Puppet::Resource::Type.new(:node, "foo")) loader.should be_nodes end it "should indicate whether no nodes are defined" do Puppet::Resource::TypeCollection.new("env").should_not be_nodes end describe "when finding nodes" do before :each do @loader = Puppet::Resource::TypeCollection.new("env") end it "should return any node whose name exactly matches the provided node name" do node = Puppet::Resource::Type.new(:node, "foo") @loader << node @loader.node("foo").should equal(node) end it "should return the first regex node whose regex matches the provided node name" do node1 = Puppet::Resource::Type.new(:node, /\w/) node2 = Puppet::Resource::Type.new(:node, /\d/) @loader << node1 << node2 @loader.node("foo10").should equal(node1) end it "should preferentially return a node whose name is string-equal over returning a node whose regex matches a provided name" do node1 = Puppet::Resource::Type.new(:node, /\w/) node2 = Puppet::Resource::Type.new(:node, "foo") @loader << node1 << node2 @loader.node("foo").should equal(node2) end end describe "when managing files" do before do @loader = Puppet::Resource::TypeCollection.new("env") Puppet::Util::LoadedFile.stubs(:new).returns stub("watched_file") end it "should have a method for specifying a file should be watched" do @loader.should respond_to(:watch_file) end it "should have a method for determining if a file is being watched" do @loader.watch_file("/foo/bar") @loader.should be_watching_file("/foo/bar") end it "should use LoadedFile to watch files" do Puppet::Util::LoadedFile.expects(:new).with("/foo/bar").returns stub("watched_file") @loader.watch_file("/foo/bar") end it "should be considered stale if any files have changed" do file1 = stub 'file1', :changed? => false file2 = stub 'file2', :changed? => true Puppet::Util::LoadedFile.expects(:new).times(2).returns(file1).then.returns(file2) @loader.watch_file("/foo/bar") @loader.watch_file("/other/bar") @loader.should be_stale end it "should not be considered stable if no files have changed" do file1 = stub 'file1', :changed? => false file2 = stub 'file2', :changed? => false Puppet::Util::LoadedFile.expects(:new).times(2).returns(file1).then.returns(file2) @loader.watch_file("/foo/bar") @loader.watch_file("/other/bar") @loader.should_not be_stale end end describe "when determining the configuration version" do before do @code = Puppet::Resource::TypeCollection.new("env") end it "should default to the current time" do time = Time.now Time.stubs(:now).returns time @code.version.should == time.to_i end it "should use the output of the environment's config_version setting if one is provided" do @code.environment.stubs(:[]).with(:config_version).returns("/my/foo") - Puppet::Util.expects(:execute).with(["/my/foo"]).returns "output\n" + Puppet::Util::Execution.expects(:execute).with(["/my/foo"]).returns "output\n" @code.version.should == "output" end it "should raise a puppet parser error if executing config_version fails" do @code.environment.stubs(:[]).with(:config_version).returns("test") - Puppet::Util.expects(:execute).raises(Puppet::ExecutionFailure.new("msg")) + Puppet::Util::Execution.expects(:execute).raises(Puppet::ExecutionFailure.new("msg")) lambda { @code.version }.should raise_error(Puppet::ParseError) end end end diff --git a/spec/unit/util/execution_stub_spec.rb b/spec/unit/util/execution_stub_spec.rb index b66f88f83..4c1e4fff1 100755 --- a/spec/unit/util/execution_stub_spec.rb +++ b/spec/unit/util/execution_stub_spec.rb @@ -1,40 +1,40 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Util::ExecutionStub do it "should use the provided stub code when 'set' is called" do Puppet::Util::ExecutionStub.set do |command, options| command.should == ['/bin/foo', 'bar'] "stub output" end Puppet::Util::ExecutionStub.current_value.should_not == nil - Puppet::Util.execute(['/bin/foo', 'bar']).should == "stub output" + Puppet::Util::Execution.execute(['/bin/foo', 'bar']).should == "stub output" end it "should automatically restore normal execution at the conclusion of each spec test" do # Note: this test relies on the previous test creating a stub. Puppet::Util::ExecutionStub.current_value.should == nil end # fails on windows, see #11740 it "should restore normal execution after 'reset' is called", :fails_on_windows => true do # Note: "true" exists at different paths in different OSes if Puppet.features.microsoft_windows? true_command = [Puppet::Util.which('cmd.exe').tr('/', '\\'), '/c', 'exit 0'] else true_command = [Puppet::Util.which('true')] end stub_call_count = 0 Puppet::Util::ExecutionStub.set do |command, options| command.should == true_command stub_call_count += 1 'stub called' end - Puppet::Util.execute(true_command).should == 'stub called' + Puppet::Util::Execution.execute(true_command).should == 'stub called' stub_call_count.should == 1 Puppet::Util::ExecutionStub.reset Puppet::Util::ExecutionStub.current_value.should == nil - Puppet::Util.execute(true_command).should == '' + Puppet::Util::Execution.execute(true_command).should == '' stub_call_count.should == 1 end end diff --git a/spec/unit/util/logging_spec.rb b/spec/unit/util/logging_spec.rb index 4c2f4cc60..8b452b950 100755 --- a/spec/unit/util/logging_spec.rb +++ b/spec/unit/util/logging_spec.rb @@ -1,119 +1,123 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/util/logging' class LoggingTester include Puppet::Util::Logging end describe Puppet::Util::Logging do before do @logger = LoggingTester.new end Puppet::Util::Log.eachlevel do |level| it "should have a method for sending '#{level}' logs" do @logger.should respond_to(level) end end it "should have a method for sending a log with a specified log level" do @logger.expects(:to_s).returns "I'm a string!" Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" and args[:level] == "loglevel" and args[:message] == "mymessage" } @logger.send_log "loglevel", "mymessage" end describe "when sending a log" do it "should use the Log's 'create' entrance method" do Puppet::Util::Log.expects(:create) @logger.notice "foo" end it "should send itself converted to a string as the log source" do @logger.expects(:to_s).returns "I'm a string!" Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" } @logger.notice "foo" end it "should queue logs sent without a specified destination" do Puppet::Util::Log.close_all Puppet::Util::Log.expects(:queuemessage) @logger.notice "foo" end it "should use the path of any provided resource type" do resource = Puppet::Type.type(:host).new :name => "foo" resource.expects(:path).returns "/path/to/host".to_sym Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/host" } resource.notice "foo" end it "should use the path of any provided resource parameter" do resource = Puppet::Type.type(:host).new :name => "foo" param = resource.parameter(:name) param.expects(:path).returns "/path/to/param".to_sym Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/param" } param.notice "foo" end it "should send the provided argument as the log message" do Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo" } @logger.notice "foo" end it "should join any provided arguments into a single string for the message" do Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo bar baz" } @logger.notice ["foo", "bar", "baz"] end [:file, :line, :tags].each do |attr| it "should include #{attr} if available" do @logger.singleton_class.send(:attr_accessor, attr) @logger.send(attr.to_s + "=", "myval") Puppet::Util::Log.expects(:create).with { |args| args[attr] == "myval" } @logger.notice "foo" end end end describe "when sending a deprecation warning" do before do @logger.clear_deprecation_warnings end - it "should the message with warn" do + it "should log the message with warn" do @logger.expects(:warning).with('foo') @logger.deprecation_warning 'foo' end - it "should only log each unique message once" do + it "should only log each offending line once" do @logger.expects(:warning).with('foo').once 5.times { @logger.deprecation_warning 'foo' } end it "should only log the first 100 messages" do (1..100).each { |i| - @logger.expects(:warning).with(i).once - @logger.deprecation_warning i + @logger.expects(:warning).with(i).once + # since the deprecation warning will only log each offending line once, we have to do some tomfoolery + # here in order to make it think each of these calls is coming from a unique call stack; we're basically + # mocking the method that it would normally use to find the call stack. + @logger.expects(:get_deprecation_offender).returns("deprecation log count test ##{i}") + @logger.deprecation_warning i } @logger.expects(:warning).with(101).never @logger.deprecation_warning 101 end end end diff --git a/spec/unit/util/suidmanager_spec.rb b/spec/unit/util/suidmanager_spec.rb index 2953a2cd4..27f71c3c5 100755 --- a/spec/unit/util/suidmanager_spec.rb +++ b/spec/unit/util/suidmanager_spec.rb @@ -1,337 +1,336 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Util::SUIDManager do let :user do Puppet::Type.type(:user).new(:name => 'name', :uid => 42, :gid => 42) end let :xids do Hash.new {|h,k| 0} end before :each do Puppet::Util::SUIDManager.stubs(:convert_xid).returns(42) Puppet::Util::SUIDManager.stubs(:initgroups) [:euid, :egid, :uid, :gid, :groups].each do |id| Process.stubs("#{id}=").with {|value| xids[id] = value} end end describe "#uid" do it "should allow setting euid/egid" do Puppet::Util::SUIDManager.egid = user[:gid] Puppet::Util::SUIDManager.euid = user[:uid] xids[:egid].should == user[:gid] xids[:euid].should == user[:uid] end end describe "#asuser" do it "should set euid/egid when root" do Process.stubs(:uid).returns(0) Puppet.features.stubs(:microsoft_windows?).returns(false) Process.stubs(:egid).returns(51) Process.stubs(:euid).returns(50) Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51) Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50) yielded = false Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) do xids[:egid].should == user[:gid] xids[:euid].should == user[:uid] yielded = true end xids[:egid].should == 51 xids[:euid].should == 50 # It's possible asuser could simply not yield, so the assertions in the # block wouldn't fail. So verify those actually got checked. yielded.should be_true end it "should not get or set euid/egid when not root" do Process.stubs(:uid).returns(1) Process.stubs(:egid).returns(51) Process.stubs(:euid).returns(50) Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {} xids.should be_empty end it "should not get or set euid/egid on Windows" do Puppet.features.stubs(:microsoft_windows?).returns true Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {} xids.should be_empty end end describe "#change_group" do describe "when changing permanently" do it "should try to change_privilege if it is supported" do Process::GID.expects(:change_privilege).with do |gid| Process.gid = gid Process.egid = gid end Puppet::Util::SUIDManager.change_group(42, true) xids[:egid].should == 42 xids[:gid].should == 42 end it "should change both egid and gid if change_privilege isn't supported" do Process::GID.stubs(:change_privilege).raises(NotImplementedError) Puppet::Util::SUIDManager.change_group(42, true) xids[:egid].should == 42 xids[:gid].should == 42 end end describe "when changing temporarily" do it "should change only egid" do Puppet::Util::SUIDManager.change_group(42, false) xids[:egid].should == 42 xids[:gid].should == 0 end end end describe "#change_user" do describe "when changing permanently" do it "should try to change_privilege if it is supported" do Process::UID.expects(:change_privilege).with do |uid| Process.uid = uid Process.euid = uid end Puppet::Util::SUIDManager.change_user(42, true) xids[:euid].should == 42 xids[:uid].should == 42 end it "should change euid and uid and groups if change_privilege isn't supported" do Process::UID.stubs(:change_privilege).raises(NotImplementedError) Puppet::Util::SUIDManager.expects(:initgroups).with(42) Puppet::Util::SUIDManager.change_user(42, true) xids[:euid].should == 42 xids[:uid].should == 42 end end describe "when changing temporarily" do it "should change only euid and groups" do Puppet::Util::SUIDManager.change_user(42, false) xids[:euid].should == 42 xids[:uid].should == 0 end it "should set euid before groups if changing to root" do Process.stubs(:euid).returns 50 when_not_root = sequence 'when_not_root' Process.expects(:euid=).in_sequence(when_not_root) Puppet::Util::SUIDManager.expects(:initgroups).in_sequence(when_not_root) Puppet::Util::SUIDManager.change_user(0, false) end it "should set groups before euid if changing from root" do Process.stubs(:euid).returns 0 when_root = sequence 'when_root' Puppet::Util::SUIDManager.expects(:initgroups).in_sequence(when_root) Process.expects(:euid=).in_sequence(when_root) Puppet::Util::SUIDManager.change_user(50, false) end end end describe "when running commands" do before :each do # We want to make sure $CHILD_STATUS is set Kernel.system '' if $CHILD_STATUS.nil? end describe "with #system" do it "should set euid/egid when root" do Process.stubs(:uid).returns(0) Puppet.features.stubs(:microsoft_windows?).returns(false) Process.stubs(:egid).returns(51) Process.stubs(:euid).returns(50) Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51) Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50) Puppet::Util::SUIDManager.expects(:change_group).with(user[:uid]) Puppet::Util::SUIDManager.expects(:change_user).with(user[:uid]) Puppet::Util::SUIDManager.expects(:change_group).with(51) Puppet::Util::SUIDManager.expects(:change_user).with(50) Kernel.expects(:system).with('blah') Puppet::Util::SUIDManager.system('blah', user[:uid], user[:gid]) end it "should not get or set euid/egid when not root" do Process.stubs(:uid).returns(1) Kernel.expects(:system).with('blah') Puppet::Util::SUIDManager.system('blah', user[:uid], user[:gid]) xids.should be_empty end it "should not get or set euid/egid on Windows" do Puppet.features.stubs(:microsoft_windows?).returns true Kernel.expects(:system).with('blah') Puppet::Util::SUIDManager.system('blah', user[:uid], user[:gid]) xids.should be_empty end end describe "with #run_and_capture" do it "should capture the output and return process status" do - Puppet::Util. - expects(:execute). - with() { |*args| + Puppet::Util::Execution. + expects(:execute).with() do |*args| args[0] == 'yay' && args[1][:combine] == true && args[1][:failonfail] == false && args[1][:uid] == user[:uid] && args[1][:gid] == user[:gid] && args[1][:override_locale] == true && args[1].has_key?(:custom_environment) - } . + end . returns('output') output = Puppet::Util::SUIDManager.run_and_capture 'yay', user[:uid], user[:gid] output.first.should == 'output' output.last.should be_a(Process::Status) end end end describe "#root?" do describe "on POSIX systems" do before :each do Puppet.features.stubs(:posix?).returns(true) Puppet.features.stubs(:microsoft_windows?).returns(false) end it "should be root if uid is 0" do Process.stubs(:uid).returns(0) Puppet::Util::SUIDManager.should be_root end it "should not be root if uid is not 0" do Process.stubs(:uid).returns(1) Puppet::Util::SUIDManager.should_not be_root end end describe "on Microsoft Windows", :if => Puppet.features.microsoft_windows? do describe "2003 without UAC" do before :each do Facter.stubs(:value).with(:kernelmajversion).returns("5.2") end it "should be root if user is a member of the Administrators group" do Sys::Admin.stubs(:get_login).returns("Administrator") Sys::Group.stubs(:members).returns(%w[Administrator]) Win32::Security.expects(:elevated_security?).never Puppet::Util::SUIDManager.should be_root end it "should not be root if the process is running as Guest" do Sys::Admin.stubs(:get_login).returns("Guest") Sys::Group.stubs(:members).returns([]) Win32::Security.expects(:elevated_security?).never Puppet::Util::SUIDManager.should_not be_root end it "should raise an exception if the process fails to open the process token" do Win32::Security.stubs(:elevated_security?).raises(Win32::Security::Error, "Access denied.") Sys::Admin.stubs(:get_login).returns("Administrator") Sys::Group.expects(:members).never lambda { Puppet::Util::SUIDManager.should raise_error(Win32::Security::Error, /Access denied./) } end end describe "2008 with UAC" do before :each do Facter.stubs(:value).with(:kernelmajversion).returns("6.0") end it "should be root if user is running with elevated privileges" do Win32::Security.stubs(:elevated_security?).returns(true) Sys::Admin.expects(:get_login).never Puppet::Util::SUIDManager.should be_root end it "should not be root if user is not running with elevated privileges" do Win32::Security.stubs(:elevated_security?).returns(false) Sys::Admin.expects(:get_login).never Puppet::Util::SUIDManager.should_not be_root end it "should raise an exception if the process fails to open the process token" do Win32::Security.stubs(:elevated_security?).raises(Win32::Security::Error, "Access denied.") Sys::Admin.expects(:get_login).never lambda { Puppet::Util::SUIDManager.should raise_error(Win32::Security::Error, /Access denied./) } end end end end end describe 'Puppet::Util::SUIDManager#groups=' do subject do Puppet::Util::SUIDManager end it "(#3419) should rescue Errno::EINVAL on OS X" do Process.expects(:groups=).raises(Errno::EINVAL, 'blew up') subject.expects(:osx_maj_ver).returns('10.7').twice subject.groups = ['list', 'of', 'groups'] end it "(#3419) should fail if an Errno::EINVAL is raised NOT on OS X" do Process.expects(:groups=).raises(Errno::EINVAL, 'blew up') subject.expects(:osx_maj_ver).returns(false) expect { subject.groups = ['list', 'of', 'groups'] }.should raise_error(Errno::EINVAL) end end diff --git a/test/util/utiltest.rb b/test/util/utiltest.rb index a142f9866..8a7ab83c7 100755 --- a/test/util/utiltest.rb +++ b/test/util/utiltest.rb @@ -1,192 +1,192 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') require 'puppettest' require 'mocha' class TestPuppetUtil < Test::Unit::TestCase include PuppetTest def test_withumask oldmask = File.umask path = tempfile # FIXME this fails on FreeBSD with a mode of 01777 Puppet::Util.withumask(000) do Dir.mkdir(path, 0777) end assert(File.stat(path).mode & 007777 == 0777, "File has the incorrect mode") assert_equal(oldmask, File.umask, "Umask was not reset") end def test_benchmark path = tempfile str = "yayness" File.open(path, "w") do |f| f.print "yayness" end # First test it with the normal args assert_nothing_raised do val = nil result = Puppet::Util.benchmark(:notice, "Read file") do val = File.read(path) end assert_equal(str, val) assert_instance_of(Float, result) end # Now test it with a passed object assert_nothing_raised do val = nil Puppet::Util.benchmark(Puppet, :notice, "Read file") do val = File.read(path) end assert_equal(str, val) end end def test_proxy klass = Class.new do attr_accessor :hash class << self attr_accessor :ohash end end klass.send(:include, Puppet::Util) klass.ohash = {} inst = klass.new inst.hash = {} assert_nothing_raised do Puppet::Util.proxy klass, :hash, "[]", "[]=", :clear, :delete end assert_nothing_raised do Puppet::Util.classproxy klass, :ohash, "[]", "[]=", :clear, :delete end assert_nothing_raised do inst[:yay] = "boo" inst["cool"] = :yayness end [:yay, "cool"].each do |var| assert_equal(inst.hash[var], inst[var], "Var #{var} did not take") end assert_nothing_raised do klass[:Yay] = "boo" klass["Cool"] = :yayness end [:Yay, "Cool"].each do |var| assert_equal(inst.hash[var], inst[var], "Var #{var} did not take") end end def test_symbolize ret = nil assert_nothing_raised { ret = Puppet::Util.symbolize("yayness") } assert_equal(:yayness, ret) assert_nothing_raised { ret = Puppet::Util.symbolize(:yayness) } assert_equal(:yayness, ret) assert_nothing_raised { ret = Puppet::Util.symbolize(43) } assert_equal(43, ret) assert_nothing_raised { ret = Puppet::Util.symbolize(nil) } assert_equal(nil, ret) end def test_execute command = tempfile File.open(command, "w") { |f| f.puts %{#!/bin/sh\n/bin/echo "$1">&1; echo "$2">&2} } File.chmod(0755, command) output = nil assert_nothing_raised do - output = Puppet::Util.execute([command, "yaytest", "funtest"]) + output = Puppet::Util::Execution.execute([command, "yaytest", "funtest"]) end assert_equal("yaytest\nfuntest\n", output) # Now try it with a single quote assert_nothing_raised do - output = Puppet::Util.execute([command, "yay'test", "funtest"]) + output = Puppet::Util::Execution.execute([command, "yay'test", "funtest"]) end assert_equal("yay'test\nfuntest\n", output) # Now make sure we can squelch output (#565) assert_nothing_raised do - output = Puppet::Util.execute([command, "yay'test", "funtest"], :squelch => true) + output = Puppet::Util::Execution.execute([command, "yay'test", "funtest"], :squelch => true) end assert_equal(nil, output) # Now test that we correctly fail if the command returns non-zero assert_raise(Puppet::ExecutionFailure) do - out = Puppet::Util.execute(["touch", "/no/such/file/could/exist"]) + out = Puppet::Util::Execution.execute(["touch", "/no/such/file/could/exist"]) end # And that we can tell it not to fail assert_nothing_raised do - out = Puppet::Util.execute(["touch", "/no/such/file/could/exist"], :failonfail => false) + out = Puppet::Util::Execution.execute(["touch", "/no/such/file/could/exist"], :failonfail => false) end if Process.uid == 0 # Make sure we correctly set our uid and gid user = nonrootuser group = nonrootgroup file = tempfile assert_nothing_raised do - Puppet::Util.execute(["touch", file], :uid => user.name, :gid => group.name) + Puppet::Util::Execution.execute(["touch", file], :uid => user.name, :gid => group.name) end assert(FileTest.exists?(file), "file was not created") assert_equal(user.uid, File.stat(file).uid, "uid was not set correctly") # We can't really check the gid, because it just behaves too # inconsistently everywhere. # assert_equal(group.gid, File.stat(file).gid, # "gid was not set correctly") end # (#565) Test the case of patricide. patricidecommand = tempfile File.open(patricidecommand, "w") { |f| f.puts %{#!/bin/bash\n/bin/bash -c 'kill -TERM \$PPID' &;\n while [ 1 ]; do echo -n ''; done;\n} } File.chmod(0755, patricidecommand) assert_nothing_raised do - output = Puppet::Util.execute([patricidecommand], :squelch => true, :failonfail => false) + output = Puppet::Util::Execution.execute([patricidecommand], :squelch => true, :failonfail => false) end assert_equal(nil, output) # See what happens if we try and read the pipe to the command... assert_raise(Puppet::ExecutionFailure) do - output = Puppet::Util.execute([patricidecommand]) + output = Puppet::Util::Execution.execute([patricidecommand]) end assert_nothing_raised do - output = Puppet::Util.execute([patricidecommand], :failonfail => false) + output = Puppet::Util::Execution.execute([patricidecommand], :failonfail => false) end end end