module Puppet::Util

MultiMatch allows multiple values to be tested at once in a case expression. This class is needed since Array does not implement the === operator to mean “each v === other.each v”.

This class is useful in situations when the Puppet Type System cannot be used (e.g. in Logging, since it needs to be able to log very early in the initialization cycle of puppet)

Typically used with the constants NOT_NIL TUPLE TRIPLE

which test against single NOT_NIL value, Array with two NOT_NIL, and Array with three NOT_NIL

Constants

ALNUM
ALPHA

From github.com/ruby/ruby/blob/v2_7_3/lib/uri/rfc2396_parser.rb#L24-L46

AbsolutePathPosix
AbsolutePathWindows
DEFAULT_POSIX_MODE

Replace a file, securely. This takes a block, and passes it the file handle of a file open for writing. Write the replacement content inside the block and it will safely replace the target file.

This method will make no changes to the target file until the content is successfully written and the block returns without raising an error.

As far as possible the state of the existing file, such as mode, is preserved. This works hard to avoid loss of any metadata, but will result in an inode change for the file.

Arguments: `filename`, `default_mode`, `staging_location`

The filename is the file we are going to replace.

The default_mode is the mode to use when the target file doesn't already exist; if the file is present we copy the existing mode/owner/group values across. The default_mode can be expressed as an octal integer, a numeric string (ie '0664') or a symbolic file mode.

The staging_location is a location to render the temporary file before moving the file to it's final location.

DEFAULT_WINDOWS_MODE
ESCAPED
HEX
HttpProxy

for backwards compatibility

PUPPET_STACK_INSERTION_FRAME
RESERVED
RFC_3986_URI_REGEX
UNRESERVED
UNSAFE

Public Class Methods

absolute_path?(path, platform=nil) click to toggle source
    # File lib/puppet/util.rb
255 def absolute_path?(path, platform=nil)
256   unless path.is_a?(String)
257     Puppet.warning("Cannot check if #{path} is an absolute path because it is a '#{path.class}' and not a String'")
258     return false
259   end
260 
261   # Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard
262   # library uses that to test what platform it's on.  Normally in Puppet we
263   # would use Puppet.features.microsoft_windows?, but this method needs to
264   # be called during the initialization of features so it can't depend on
265   # that.
266   #
267   # @deprecated Use ruby's built-in methods to determine if a path is absolute.
268   platform ||= Puppet::Util::Platform.windows? ? :windows : :posix
269   regex = case platform
270           when :windows
271             AbsolutePathWindows
272           when :posix
273             AbsolutePathPosix
274           else
275             raise Puppet::DevError, _("unknown platform %{platform} in absolute_path") % { platform: platform }
276           end
277 
278   !! (path =~ regex)
279 end
benchmark(*args) { || ... } click to toggle source

execute a block of work and based on the logging level provided, log the provided message with the seconds taken The message 'msg' should include string ' in %{seconds} seconds' as part of the message and any content should escape any percent signs '%' so that they are not interpreted as formatting commands

escaped_str = str.gsub(/%/, '%%')

@param msg [String] the message to be formated to assigned the %{seconds} seconds take to execute,

other percent signs '%' need to be escaped

@param level [Symbol] the logging level for this message @param object [Object] The object use for logging the message

    # File lib/puppet/util.rb
167 def benchmark(*args)
168   msg = args.pop
169   level = args.pop
170   object = if args.empty?
171              if respond_to?(level)
172                self
173              else
174                Puppet
175              end
176            else
177              args.pop
178            end
179 
180   #TRANSLATORS 'benchmark' is a method name and should not be translated
181   raise Puppet::DevError, _("Failed to provide level to benchmark") unless level
182 
183   unless level == :none or object.respond_to? level
184     raise Puppet::DevError, _("Benchmarked object does not respond to %{value}") % { value: level }
185   end
186 
187   # Only benchmark if our log level is high enough
188   if level != :none and Puppet::Util::Log.sendlevel?(level)
189     seconds = Benchmark.realtime {
190       yield
191     }
192     object.send(level, msg % { seconds: "%0.2f" % seconds })
193     return seconds
194   else
195     yield
196   end
197 end
chuser() click to toggle source

Change the process to a different user

    # File lib/puppet/util.rb
107 def self.chuser
108   group = Puppet[:group]
109   if group
110     begin
111       Puppet::Util::SUIDManager.change_group(group, true)
112     rescue => detail
113       Puppet.warning _("could not change to group %{group}: %{detail}") % { group: group.inspect, detail: detail }
114       $stderr.puts _("could not change to group %{group}") % { group: group.inspect }
115 
116       # Don't exit on failed group changes, since it's
117       # not fatal
118       #exit(74)
119     end
120   end
121 
122   user = Puppet[:user]
123   if user
124     begin
125       Puppet::Util::SUIDManager.change_user(user, true)
126     rescue => detail
127       $stderr.puts _("Could not change to user %{user}: %{detail}") % { user: user, detail: detail }
128       exit(74)
129     end
130   end
131 end
clear_environment(mode = default_env) click to toggle source

@deprecated Use ENV instead @api private

   # File lib/puppet/util.rb
59 def clear_environment(mode = default_env)
60   ENV.clear
61 end
create_erb(content) click to toggle source
   # File lib/puppet/util.rb
38 def create_erb(content)
39   ERB.new(content, trim_mode: '-')
40 end
default_env() click to toggle source
   # File lib/puppet/util.rb
31 def default_env
32   Puppet.features.microsoft_windows? ?
33     :windows :
34     :posix
35 end
deterministic_rand(seed,max) click to toggle source
    # File lib/puppet/util.rb
719 def deterministic_rand(seed,max)
720   deterministic_rand_int(seed, max).to_s
721 end
deterministic_rand_int(seed,max) click to toggle source
    # File lib/puppet/util.rb
724 def deterministic_rand_int(seed,max)
725   Random.new(seed).rand(max)
726 end
exit_on_fail(message, code = 1) { || ... } click to toggle source

Executes a block of code, wrapped with some special exception handling. Causes the ruby interpreter to

exit if the block throws an exception.

@api public @param [String] message a message to log if the block fails @param [Integer] code the exit code that the ruby interpreter should return if the block fails @yield

    # File lib/puppet/util.rb
700 def exit_on_fail(message, code = 1)
701   yield
702 # First, we need to check and see if we are catching a SystemExit error.  These will be raised
703 #  when we daemonize/fork, and they do not necessarily indicate a failure case.
704 rescue SystemExit => err
705   raise err
706 
707 # Now we need to catch *any* other kind of exception, because we may be calling third-party
708 #  code (e.g. webrick), and we have no idea what they might throw.
709 rescue Exception => err
710   ## NOTE: when debugging spec failures, these two lines can be very useful
711   #puts err.inspect
712   #puts Puppet::Util.pretty_backtrace(err.backtrace)
713   Puppet.log_exception(err, "#{message}: #{err}")
714   Puppet::Util::Log.force_flushqueue()
715   exit(code)
716 end
format_backtrace_array(primary_stack, puppetstack = []) click to toggle source

arguments may be a Ruby stack, with an optional Puppet stack argument, or just a Puppet stack. stacks may be an Array of Strings “/foo.rb:0 in `blah'” or an Array of Arrays that represent a frame: [“/foo.pp”, 0]

    # File lib/puppet/util.rb
532 def self.format_backtrace_array(primary_stack, puppetstack = [])
533   primary_stack.flat_map do |frame|
534     frame = format_puppetstack_frame(frame) if frame.is_a?(Array)
535     primary_frame = resolve_stackframe(frame)
536 
537     if primary_frame =~ PUPPET_STACK_INSERTION_FRAME && !puppetstack.empty?
538       [resolve_stackframe(format_puppetstack_frame(puppetstack.shift)),
539        primary_frame]
540     else
541       primary_frame
542     end
543   end
544 end
format_puppetstack_frame(file_and_lineno) click to toggle source
    # File lib/puppet/util.rb
556 def self.format_puppetstack_frame(file_and_lineno)
557   file_and_lineno.join(':')
558 end
get_env(name, mode = default_env) click to toggle source

@deprecated Use ENV instead @api private

   # File lib/puppet/util.rb
45 def get_env(name, mode = default_env)
46   ENV[name]
47 end
get_environment(mode = default_env) click to toggle source

@deprecated Use ENV instead @api private

   # File lib/puppet/util.rb
52 def get_environment(mode = default_env)
53   ENV.to_hash
54 end
logmethods(klass, useself = true) click to toggle source

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.

    # File lib/puppet/util.rb
136 def self.logmethods(klass, useself = true)
137   Puppet::Util::Log.eachlevel { |level|
138     klass.send(:define_method, level, proc { |args|
139       args = args.join(" ") if args.is_a?(Array)
140       if useself
141 
142         Puppet::Util::Log.create(
143           :level => level,
144           :source => self,
145           :message => args
146         )
147       else
148 
149         Puppet::Util::Log.create(
150           :level => level,
151           :message => args
152         )
153       end
154     })
155   }
156 end
merge_environment(env_hash, mode = default_env) click to toggle source

@deprecated Use ENV instead @api private

   # File lib/puppet/util.rb
73 def merge_environment(env_hash, mode = default_env)
74   ENV.merge!(hash.transform_keys(&:to_s))
75 end
path_to_uri(path) click to toggle source

Convert a path to a file URI

    # File lib/puppet/util.rb
283 def path_to_uri(path)
284   return unless path
285 
286   params = { :scheme => 'file' }
287 
288   if Puppet::Util::Platform.windows?
289     path = path.tr('\\', '/')
290 
291     unc = /^\/\/([^\/]+)(\/.+)/.match(path)
292     if unc
293       params[:host] = unc[1]
294       path = unc[2]
295     elsif path =~ /^[a-z]:\//i
296       path = '/' + path
297     end
298   end
299 
300   # have to split *after* any relevant escaping
301   params[:path], params[:query] = uri_encode(path).split('?')
302   search_for_fragment = params[:query] ? :query : :path
303   if params[search_for_fragment].include?('#')
304     params[search_for_fragment], _, params[:fragment] = params[search_for_fragment].rpartition('#')
305   end
306 
307   begin
308     URI::Generic.build(params)
309   rescue => detail
310     raise Puppet::Error, _("Failed to convert '%{path}' to URI: %{detail}") % { path: path, detail: detail }, detail.backtrace
311   end
312 end
pretty_backtrace(backtrace = caller(1), puppetstack = []) click to toggle source

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)

    # File lib/puppet/util.rb
524 def self.pretty_backtrace(backtrace = caller(1), puppetstack = [])
525   format_backtrace_array(backtrace, puppetstack).join("\n")
526 end
replace_file(file, default_mode, staging_location: nil, validate_callback: nil) { |tempfile| ... } click to toggle source
    # File lib/puppet/util.rb
586 def replace_file(file, default_mode, staging_location: nil, validate_callback: nil, &block)
587   raise Puppet::DevError, _("replace_file requires a block") unless block_given?
588 
589   if default_mode
590     unless valid_symbolic_mode?(default_mode)
591       raise Puppet::DevError, _("replace_file default_mode: %{default_mode} is invalid") % { default_mode: default_mode }
592     end
593 
594     mode = symbolic_mode_to_int(normalize_symbolic_mode(default_mode))
595   else
596     if Puppet::Util::Platform.windows?
597       mode = DEFAULT_WINDOWS_MODE
598     else
599       mode = DEFAULT_POSIX_MODE
600     end
601   end
602 
603   begin
604     file = Puppet::FileSystem.pathname(file)
605 
606     # encoding for Uniquefile is not important here because the caller writes to it as it sees fit
607     if staging_location
608       tempfile = Puppet::FileSystem::Uniquefile.new(Puppet::FileSystem.basename_string(file), staging_location)
609     else
610       tempfile = Puppet::FileSystem::Uniquefile.new(Puppet::FileSystem.basename_string(file), Puppet::FileSystem.dir_string(file))
611     end
612 
613 
614     effective_mode =
615     if !Puppet::Util::Platform.windows?
616       # Grab the current file mode, and fall back to the defaults.
617 
618       if Puppet::FileSystem.exist?(file)
619         stat = Puppet::FileSystem.lstat(file)
620         tempfile.chown(stat.uid, stat.gid)
621         stat.mode
622       else
623         mode
624       end
625     end
626 
627     # OK, now allow the caller to write the content of the file.
628     yield tempfile
629 
630     if effective_mode
631       # We only care about the bottom four slots, which make the real mode,
632       # and not the rest of the platform stat call fluff and stuff.
633       tempfile.chmod(effective_mode & 07777)
634     end
635 
636     # Now, make sure the data (which includes the mode) is safe on disk.
637     tempfile.flush
638     begin
639       tempfile.fsync
640     rescue NotImplementedError
641       # fsync may not be implemented by Ruby on all platforms, but
642       # there is absolutely no recovery path if we detect that.  So, we just
643       # ignore the return code.
644       #
645       # However, don't be fooled: that is accepting that we are running in
646       # an unsafe fashion.  If you are porting to a new platform don't stub
647       # that out.
648     end
649 
650     tempfile.close
651 
652     if validate_callback
653       validate_callback.call(tempfile.path)
654     end
655 
656     if Puppet::Util::Platform.windows?
657       # Windows ReplaceFile needs a file to exist, so touch handles this
658       if !Puppet::FileSystem.exist?(file)
659         Puppet::FileSystem.touch(file)
660         if mode
661           Puppet::Util::Windows::Security.set_mode(mode, Puppet::FileSystem.path_string(file))
662         end
663       end
664       # Yes, the arguments are reversed compared to the rename in the rest
665       # of the world.
666       Puppet::Util::Windows::File.replace_file(FileSystem.path_string(file), tempfile.path)
667 
668     else
669       # MRI Ruby checks for this and raises an error, while JRuby removes the directory
670       # and replaces it with a file. This makes the our version of replace_file() consistent
671       if Puppet::FileSystem.exist?(file) && Puppet::FileSystem.directory?(file)
672         raise Errno::EISDIR, _("Is a directory: %{directory}") % { directory: file }
673       end
674       File.rename(tempfile.path, Puppet::FileSystem.path_string(file))
675     end
676   ensure
677     # in case an error occurred before we renamed the temp file, make sure it
678     # gets deleted
679     if tempfile
680       tempfile.close!
681     end
682   end
683 
684 
685   # Ideally, we would now fsync the directory as well, but Ruby doesn't
686   # have support for that, and it doesn't matter /that/ much...
687 
688   # Return something true, and possibly useful.
689   file
690 end
resolve_stackframe(frame) click to toggle source
    # File lib/puppet/util.rb
546 def self.resolve_stackframe(frame)
547   _, path, rest = /^(.*):(\d+.*)$/.match(frame).to_a
548   if path
549     path = Pathname(path).realpath rescue path
550     "#{path}:#{rest}"
551   else
552     frame
553   end
554 end
rfc2396_escape(str) click to toggle source
    # File lib/puppet/util.rb
455 def rfc2396_escape(str)
456   str.gsub(UNSAFE) do |match|
457     tmp = String.new
458     match.each_byte do |uc|
459       tmp << sprintf('%%%02X', uc)
460     end
461     tmp
462   end.force_encoding(Encoding::US_ASCII)
463 end
safe_posix_fork(stdin=$stdin, stdout=$stdout, stderr=$stderr, &block) click to toggle source
    # File lib/puppet/util.rb
473 def safe_posix_fork(stdin=$stdin, stdout=$stdout, stderr=$stderr, &block)
474   child_pid = Kernel.fork do
475     STDIN.reopen(stdin)
476     STDOUT.reopen(stdout)
477     STDERR.reopen(stderr)
478 
479     $stdin = STDIN
480     $stdout = STDOUT
481     $stderr = STDERR
482 
483     begin
484       Dir.foreach('/proc/self/fd') do |f|
485         if f != '.' && f != '..' && f.to_i >= 3
486           IO::new(f.to_i).close rescue nil
487         end
488       end
489     rescue Errno::ENOENT, Errno::ENOTDIR # /proc/self/fd not found, /proc/self not a dir
490       3.upto(256){|fd| IO::new(fd).close rescue nil}
491     end
492 
493     block.call if block
494   end
495   child_pid
496 end
set_env(name, value = nil, mode = default_env) click to toggle source

@deprecated Use ENV instead @api private

   # File lib/puppet/util.rb
66 def set_env(name, value = nil, mode = default_env)
67   ENV[name] = value
68 end
skip_external_facts() { || ... } click to toggle source

Executes a block of code, wrapped around Facter.load_external(false) and Facter.load_external(true) which will cause Facter to not evaluate external facts.

    # File lib/puppet/util.rb
731 def skip_external_facts
732   return yield unless Puppet.runtime[:facter].load_external?
733 
734   begin
735     Puppet.runtime[:facter].load_external(false)
736     yield
737   ensure
738     Puppet.runtime[:facter].load_external(true)
739   end
740 end
symbolizehash(hash) click to toggle source
    # File lib/puppet/util.rb
499 def symbolizehash(hash)
500   newhash = {}
501   hash.each do |name, val|
502     name = name.intern if name.respond_to? :intern
503     newhash[name] = val
504   end
505   newhash
506 end
thinmark() { || ... } click to toggle source

Just benchmark, with no logging.

    # File lib/puppet/util.rb
510 def thinmark
511   seconds = Benchmark.realtime {
512     yield
513   }
514 
515   seconds
516 end
uri_encode(path, opts = { :allow_fragment => false }) click to toggle source

Percent-encodes a URI string per RFC3986 - tools.ietf.org/html/rfc3986

Properly handles escaping rules for paths, query strings and fragments independently

The output is safe to pass to URI.parse or URI::Generic.build and will correctly round-trip through URI.unescape

@param [String path] A URI string that may be in the form of:

http://foo.com/bar?query
file://tmp/foo bar
//foo.com/bar?query
/bar?query
bar?query
bar
.
C:\Windows\Temp

Note that with no specified scheme, authority or query parameter delimiter
? that a naked string will be treated as a path.

Note that if query parameters need to contain data such as & or =
that this method should not be used, as there is no way to differentiate
query parameter data from query delimiters when multiple parameters
are specified

@param [Hash{Symbol=>String} opts] Options to alter encoding @option opts [Array<Symbol>] :allow_fragment defaults to false. When false

will treat # as part of a path or query and not a fragment delimiter

@return [String] a new string containing appropriate portions of the URI

encoded per the rules of RFC3986.
In particular,
path will not encode +, but will encode space as %20
query will encode + as %2B and space as %20
fragment behaves like query
    # File lib/puppet/util.rb
409 def uri_encode(path, opts = { :allow_fragment => false })
410   raise ArgumentError.new(_('path may not be nil')) if path.nil?
411 
412   # ensure string starts as UTF-8 for the sake of Ruby 1.9.3
413   encoded = String.new.encode!(Encoding::UTF_8)
414 
415   # parse uri into named matches, then reassemble properly encoded
416   parts = path.match(RFC_3986_URI_REGEX)
417 
418   encoded += parts[:scheme] unless parts[:scheme].nil?
419   encoded += parts[:authority] unless parts[:authority].nil?
420 
421   # path requires space to be encoded as %20 (NEVER +)
422   # + should be left unencoded
423   # URI::parse and URI::Generic.build don't like paths encoded with CGI.escape
424   # URI.escape does not change / to %2F and : to %3A like CGI.escape
425   #
426   encoded += rfc2396_escape(parts[:path]) unless parts[:path].nil?
427 
428   # each query parameter
429   if !parts[:query].nil?
430     query_string = parts[:query].split('&').map do |pair|
431       # can optionally be separated by an =
432       pair.split('=').map do |v|
433         uri_query_encode(v)
434       end.join('=')
435     end.join('&')
436     encoded += '?' + query_string
437   end
438 
439   encoded += ((opts[:allow_fragment] ? '#' : '%23') + uri_query_encode(parts[:fragment])) unless parts[:fragment].nil?
440 
441   encoded
442 end
uri_query_encode(query_string) click to toggle source

Percent-encodes a URI query parameter per RFC3986 - tools.ietf.org/html/rfc3986

The output will correctly round-trip through URI.unescape

@param [String query_string] A URI query parameter that may contain reserved

characters that must be percent encoded for the key or value to be
properly decoded as part of a larger query string:

query
encodes as : query

query_with_special=chars like&and * and# plus+this
encodes as:
query_with_special%3Dchars%20like%26and%20%2A%20and%23%20plus%2Bthis

Note: Also usable by fragments, but not suitable for paths

@return [String] a new string containing an encoded query string per the

rules of RFC3986.

In particular,
query will encode + as %2B and space as %20
    # File lib/puppet/util.rb
359 def uri_query_encode(query_string)
360   return nil if query_string.nil?
361 
362   # query can encode space to %20 OR +
363   # + MUST be encoded as %2B
364   # in RFC3968 both query and fragment are defined as:
365   # = *( pchar / "/" / "?" )
366   # CGI.escape turns space into + which is the most backward compatible
367   # however it doesn't roundtrip through URI.unescape which prefers %20
368   CGI.escape(query_string).gsub('+', '%20')
369 end
uri_to_path(uri) click to toggle source

Get the path component of a URI

    # File lib/puppet/util.rb
316 def uri_to_path(uri)
317   return unless uri.is_a?(URI)
318 
319   # CGI.unescape doesn't handle space rules properly in uri paths
320   # URI.unescape does, but returns strings in their original encoding
321   path = uri_unescape(uri.path.encode(Encoding::UTF_8))
322 
323   if Puppet::Util::Platform.windows? && uri.scheme == 'file'
324     if uri.host && !uri.host.empty?
325       path = "//#{uri.host}" + path # UNC
326     else
327       path.sub!(/^\//, '')
328     end
329   end
330 
331   path
332 end
uri_unescape(str) click to toggle source
    # File lib/puppet/util.rb
466 def uri_unescape(str)
467   enc = str.encoding
468   enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
469   str.gsub(ESCAPED) { [$&[1, 2]].pack('H2').force_encoding(enc) }
470 end
which(bin) click to toggle source

Resolve a path for an executable to the absolute path. This tries to behave in the same manner as the unix `which` command and uses the `PATH` environment variable.

@api public @param bin [String] the name of the executable to find. @return [String] the absolute path to the found executable.

    # File lib/puppet/util.rb
207 def which(bin)
208   if absolute_path?(bin)
209     return bin if FileTest.file? bin and FileTest.executable? bin
210   else
211     exts = ENV['PATHEXT']
212     exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD]
213     ENV['PATH'].split(File::PATH_SEPARATOR).each do |dir|
214       begin
215         dest = File.expand_path(File.join(dir, bin))
216       rescue ArgumentError => e
217         # if the user's PATH contains a literal tilde (~) character and HOME is not set, we may get
218         # an ArgumentError here.  Let's check to see if that is the case; if not, re-raise whatever error
219         # was thrown.
220         if e.to_s =~ /HOME/ and (ENV['HOME'].nil? || ENV['HOME'] == "")
221           # if we get here they have a tilde in their PATH.  We'll issue a single warning about this and then
222           # ignore this path element and carry on with our lives.
223           #TRANSLATORS PATH and HOME are environment variables and should not be translated
224           Puppet::Util::Warnings.warnonce(_("PATH contains a ~ character, and HOME is not set; ignoring PATH element '%{dir}'.") % { dir: dir })
225         elsif e.to_s =~ /doesn't exist|can't find user/
226           # ...otherwise, we just skip the non-existent entry, and do nothing.
227           #TRANSLATORS PATH is an environment variable and should not be translated
228           Puppet::Util::Warnings.warnonce(_("Couldn't expand PATH containing a ~ character; ignoring PATH element '%{dir}'.") % { dir: dir })
229         else
230           raise
231         end
232       else
233         if Puppet::Util::Platform.windows? && File.extname(dest).empty?
234           exts.each do |ext|
235             destext = File.expand_path(dest + ext)
236             return destext if FileTest.file? destext and FileTest.executable? destext
237           end
238         end
239         return dest if FileTest.file? dest and FileTest.executable? dest
240       end
241     end
242   end
243   nil
244 end
withenv(hash, mode = :posix) { || ... } click to toggle source

Run some code with a specific environment. Resets the environment back to what it was at the end of the code.

@param hash [Hash{String,Symbol => String}] Environment variables to override the current environment. @param mode [Symbol] ignored

   # File lib/puppet/util.rb
83 def withenv(hash, mode = :posix)
84   saved = ENV.to_hash
85   begin
86     ENV.merge!(hash.transform_keys(&:to_s))
87     yield
88   ensure
89     ENV.replace(saved)
90   end
91 end
withumask(mask) { || ... } click to toggle source

Execute a given chunk of code with a new umask.

    # File lib/puppet/util.rb
 95 def self.withumask(mask)
 96   cur = File.umask(mask)
 97 
 98   begin
 99     yield
100   ensure
101     File.umask(cur)
102   end
103 end

Private Instance Methods

absolute_path?(path, platform=nil) click to toggle source
    # File lib/puppet/util.rb
255 def absolute_path?(path, platform=nil)
256   unless path.is_a?(String)
257     Puppet.warning("Cannot check if #{path} is an absolute path because it is a '#{path.class}' and not a String'")
258     return false
259   end
260 
261   # Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard
262   # library uses that to test what platform it's on.  Normally in Puppet we
263   # would use Puppet.features.microsoft_windows?, but this method needs to
264   # be called during the initialization of features so it can't depend on
265   # that.
266   #
267   # @deprecated Use ruby's built-in methods to determine if a path is absolute.
268   platform ||= Puppet::Util::Platform.windows? ? :windows : :posix
269   regex = case platform
270           when :windows
271             AbsolutePathWindows
272           when :posix
273             AbsolutePathPosix
274           else
275             raise Puppet::DevError, _("unknown platform %{platform} in absolute_path") % { platform: platform }
276           end
277 
278   !! (path =~ regex)
279 end
benchmark(*args) { || ... } click to toggle source

execute a block of work and based on the logging level provided, log the provided message with the seconds taken The message 'msg' should include string ' in %{seconds} seconds' as part of the message and any content should escape any percent signs '%' so that they are not interpreted as formatting commands

escaped_str = str.gsub(/%/, '%%')

@param msg [String] the message to be formated to assigned the %{seconds} seconds take to execute,

other percent signs '%' need to be escaped

@param level [Symbol] the logging level for this message @param object [Object] The object use for logging the message

    # File lib/puppet/util.rb
167 def benchmark(*args)
168   msg = args.pop
169   level = args.pop
170   object = if args.empty?
171              if respond_to?(level)
172                self
173              else
174                Puppet
175              end
176            else
177              args.pop
178            end
179 
180   #TRANSLATORS 'benchmark' is a method name and should not be translated
181   raise Puppet::DevError, _("Failed to provide level to benchmark") unless level
182 
183   unless level == :none or object.respond_to? level
184     raise Puppet::DevError, _("Benchmarked object does not respond to %{value}") % { value: level }
185   end
186 
187   # Only benchmark if our log level is high enough
188   if level != :none and Puppet::Util::Log.sendlevel?(level)
189     seconds = Benchmark.realtime {
190       yield
191     }
192     object.send(level, msg % { seconds: "%0.2f" % seconds })
193     return seconds
194   else
195     yield
196   end
197 end
clear_environment(mode = default_env) click to toggle source

@deprecated Use ENV instead @api private

   # File lib/puppet/util.rb
59 def clear_environment(mode = default_env)
60   ENV.clear
61 end
create_erb(content) click to toggle source
   # File lib/puppet/util.rb
38 def create_erb(content)
39   ERB.new(content, trim_mode: '-')
40 end
default_env() click to toggle source
   # File lib/puppet/util.rb
31 def default_env
32   Puppet.features.microsoft_windows? ?
33     :windows :
34     :posix
35 end
deterministic_rand(seed,max) click to toggle source
    # File lib/puppet/util.rb
719 def deterministic_rand(seed,max)
720   deterministic_rand_int(seed, max).to_s
721 end
deterministic_rand_int(seed,max) click to toggle source
    # File lib/puppet/util.rb
724 def deterministic_rand_int(seed,max)
725   Random.new(seed).rand(max)
726 end
exit_on_fail(message, code = 1) { || ... } click to toggle source

Executes a block of code, wrapped with some special exception handling. Causes the ruby interpreter to

exit if the block throws an exception.

@api public @param [String] message a message to log if the block fails @param [Integer] code the exit code that the ruby interpreter should return if the block fails @yield

    # File lib/puppet/util.rb
700 def exit_on_fail(message, code = 1)
701   yield
702 # First, we need to check and see if we are catching a SystemExit error.  These will be raised
703 #  when we daemonize/fork, and they do not necessarily indicate a failure case.
704 rescue SystemExit => err
705   raise err
706 
707 # Now we need to catch *any* other kind of exception, because we may be calling third-party
708 #  code (e.g. webrick), and we have no idea what they might throw.
709 rescue Exception => err
710   ## NOTE: when debugging spec failures, these two lines can be very useful
711   #puts err.inspect
712   #puts Puppet::Util.pretty_backtrace(err.backtrace)
713   Puppet.log_exception(err, "#{message}: #{err}")
714   Puppet::Util::Log.force_flushqueue()
715   exit(code)
716 end
get_env(name, mode = default_env) click to toggle source

@deprecated Use ENV instead @api private

   # File lib/puppet/util.rb
45 def get_env(name, mode = default_env)
46   ENV[name]
47 end
get_environment(mode = default_env) click to toggle source

@deprecated Use ENV instead @api private

   # File lib/puppet/util.rb
52 def get_environment(mode = default_env)
53   ENV.to_hash
54 end
merge_environment(env_hash, mode = default_env) click to toggle source

@deprecated Use ENV instead @api private

   # File lib/puppet/util.rb
73 def merge_environment(env_hash, mode = default_env)
74   ENV.merge!(hash.transform_keys(&:to_s))
75 end
path_to_uri(path) click to toggle source

Convert a path to a file URI

    # File lib/puppet/util.rb
283 def path_to_uri(path)
284   return unless path
285 
286   params = { :scheme => 'file' }
287 
288   if Puppet::Util::Platform.windows?
289     path = path.tr('\\', '/')
290 
291     unc = /^\/\/([^\/]+)(\/.+)/.match(path)
292     if unc
293       params[:host] = unc[1]
294       path = unc[2]
295     elsif path =~ /^[a-z]:\//i
296       path = '/' + path
297     end
298   end
299 
300   # have to split *after* any relevant escaping
301   params[:path], params[:query] = uri_encode(path).split('?')
302   search_for_fragment = params[:query] ? :query : :path
303   if params[search_for_fragment].include?('#')
304     params[search_for_fragment], _, params[:fragment] = params[search_for_fragment].rpartition('#')
305   end
306 
307   begin
308     URI::Generic.build(params)
309   rescue => detail
310     raise Puppet::Error, _("Failed to convert '%{path}' to URI: %{detail}") % { path: path, detail: detail }, detail.backtrace
311   end
312 end
replace_file(file, default_mode, staging_location: nil, validate_callback: nil) { |tempfile| ... } click to toggle source
    # File lib/puppet/util.rb
586 def replace_file(file, default_mode, staging_location: nil, validate_callback: nil, &block)
587   raise Puppet::DevError, _("replace_file requires a block") unless block_given?
588 
589   if default_mode
590     unless valid_symbolic_mode?(default_mode)
591       raise Puppet::DevError, _("replace_file default_mode: %{default_mode} is invalid") % { default_mode: default_mode }
592     end
593 
594     mode = symbolic_mode_to_int(normalize_symbolic_mode(default_mode))
595   else
596     if Puppet::Util::Platform.windows?
597       mode = DEFAULT_WINDOWS_MODE
598     else
599       mode = DEFAULT_POSIX_MODE
600     end
601   end
602 
603   begin
604     file = Puppet::FileSystem.pathname(file)
605 
606     # encoding for Uniquefile is not important here because the caller writes to it as it sees fit
607     if staging_location
608       tempfile = Puppet::FileSystem::Uniquefile.new(Puppet::FileSystem.basename_string(file), staging_location)
609     else
610       tempfile = Puppet::FileSystem::Uniquefile.new(Puppet::FileSystem.basename_string(file), Puppet::FileSystem.dir_string(file))
611     end
612 
613 
614     effective_mode =
615     if !Puppet::Util::Platform.windows?
616       # Grab the current file mode, and fall back to the defaults.
617 
618       if Puppet::FileSystem.exist?(file)
619         stat = Puppet::FileSystem.lstat(file)
620         tempfile.chown(stat.uid, stat.gid)
621         stat.mode
622       else
623         mode
624       end
625     end
626 
627     # OK, now allow the caller to write the content of the file.
628     yield tempfile
629 
630     if effective_mode
631       # We only care about the bottom four slots, which make the real mode,
632       # and not the rest of the platform stat call fluff and stuff.
633       tempfile.chmod(effective_mode & 07777)
634     end
635 
636     # Now, make sure the data (which includes the mode) is safe on disk.
637     tempfile.flush
638     begin
639       tempfile.fsync
640     rescue NotImplementedError
641       # fsync may not be implemented by Ruby on all platforms, but
642       # there is absolutely no recovery path if we detect that.  So, we just
643       # ignore the return code.
644       #
645       # However, don't be fooled: that is accepting that we are running in
646       # an unsafe fashion.  If you are porting to a new platform don't stub
647       # that out.
648     end
649 
650     tempfile.close
651 
652     if validate_callback
653       validate_callback.call(tempfile.path)
654     end
655 
656     if Puppet::Util::Platform.windows?
657       # Windows ReplaceFile needs a file to exist, so touch handles this
658       if !Puppet::FileSystem.exist?(file)
659         Puppet::FileSystem.touch(file)
660         if mode
661           Puppet::Util::Windows::Security.set_mode(mode, Puppet::FileSystem.path_string(file))
662         end
663       end
664       # Yes, the arguments are reversed compared to the rename in the rest
665       # of the world.
666       Puppet::Util::Windows::File.replace_file(FileSystem.path_string(file), tempfile.path)
667 
668     else
669       # MRI Ruby checks for this and raises an error, while JRuby removes the directory
670       # and replaces it with a file. This makes the our version of replace_file() consistent
671       if Puppet::FileSystem.exist?(file) && Puppet::FileSystem.directory?(file)
672         raise Errno::EISDIR, _("Is a directory: %{directory}") % { directory: file }
673       end
674       File.rename(tempfile.path, Puppet::FileSystem.path_string(file))
675     end
676   ensure
677     # in case an error occurred before we renamed the temp file, make sure it
678     # gets deleted
679     if tempfile
680       tempfile.close!
681     end
682   end
683 
684 
685   # Ideally, we would now fsync the directory as well, but Ruby doesn't
686   # have support for that, and it doesn't matter /that/ much...
687 
688   # Return something true, and possibly useful.
689   file
690 end
rfc2396_escape(str) click to toggle source
    # File lib/puppet/util.rb
455 def rfc2396_escape(str)
456   str.gsub(UNSAFE) do |match|
457     tmp = String.new
458     match.each_byte do |uc|
459       tmp << sprintf('%%%02X', uc)
460     end
461     tmp
462   end.force_encoding(Encoding::US_ASCII)
463 end
safe_posix_fork(stdin=$stdin, stdout=$stdout, stderr=$stderr, &block) click to toggle source
    # File lib/puppet/util.rb
473 def safe_posix_fork(stdin=$stdin, stdout=$stdout, stderr=$stderr, &block)
474   child_pid = Kernel.fork do
475     STDIN.reopen(stdin)
476     STDOUT.reopen(stdout)
477     STDERR.reopen(stderr)
478 
479     $stdin = STDIN
480     $stdout = STDOUT
481     $stderr = STDERR
482 
483     begin
484       Dir.foreach('/proc/self/fd') do |f|
485         if f != '.' && f != '..' && f.to_i >= 3
486           IO::new(f.to_i).close rescue nil
487         end
488       end
489     rescue Errno::ENOENT, Errno::ENOTDIR # /proc/self/fd not found, /proc/self not a dir
490       3.upto(256){|fd| IO::new(fd).close rescue nil}
491     end
492 
493     block.call if block
494   end
495   child_pid
496 end
set_env(name, value = nil, mode = default_env) click to toggle source

@deprecated Use ENV instead @api private

   # File lib/puppet/util.rb
66 def set_env(name, value = nil, mode = default_env)
67   ENV[name] = value
68 end
skip_external_facts() { || ... } click to toggle source

Executes a block of code, wrapped around Facter.load_external(false) and Facter.load_external(true) which will cause Facter to not evaluate external facts.

    # File lib/puppet/util.rb
731 def skip_external_facts
732   return yield unless Puppet.runtime[:facter].load_external?
733 
734   begin
735     Puppet.runtime[:facter].load_external(false)
736     yield
737   ensure
738     Puppet.runtime[:facter].load_external(true)
739   end
740 end
symbolizehash(hash) click to toggle source
    # File lib/puppet/util.rb
499 def symbolizehash(hash)
500   newhash = {}
501   hash.each do |name, val|
502     name = name.intern if name.respond_to? :intern
503     newhash[name] = val
504   end
505   newhash
506 end
thinmark() { || ... } click to toggle source

Just benchmark, with no logging.

    # File lib/puppet/util.rb
510 def thinmark
511   seconds = Benchmark.realtime {
512     yield
513   }
514 
515   seconds
516 end
uri_encode(path, opts = { :allow_fragment => false }) click to toggle source

Percent-encodes a URI string per RFC3986 - tools.ietf.org/html/rfc3986

Properly handles escaping rules for paths, query strings and fragments independently

The output is safe to pass to URI.parse or URI::Generic.build and will correctly round-trip through URI.unescape

@param [String path] A URI string that may be in the form of:

http://foo.com/bar?query
file://tmp/foo bar
//foo.com/bar?query
/bar?query
bar?query
bar
.
C:\Windows\Temp

Note that with no specified scheme, authority or query parameter delimiter
? that a naked string will be treated as a path.

Note that if query parameters need to contain data such as & or =
that this method should not be used, as there is no way to differentiate
query parameter data from query delimiters when multiple parameters
are specified

@param [Hash{Symbol=>String} opts] Options to alter encoding @option opts [Array<Symbol>] :allow_fragment defaults to false. When false

will treat # as part of a path or query and not a fragment delimiter

@return [String] a new string containing appropriate portions of the URI

encoded per the rules of RFC3986.
In particular,
path will not encode +, but will encode space as %20
query will encode + as %2B and space as %20
fragment behaves like query
    # File lib/puppet/util.rb
409 def uri_encode(path, opts = { :allow_fragment => false })
410   raise ArgumentError.new(_('path may not be nil')) if path.nil?
411 
412   # ensure string starts as UTF-8 for the sake of Ruby 1.9.3
413   encoded = String.new.encode!(Encoding::UTF_8)
414 
415   # parse uri into named matches, then reassemble properly encoded
416   parts = path.match(RFC_3986_URI_REGEX)
417 
418   encoded += parts[:scheme] unless parts[:scheme].nil?
419   encoded += parts[:authority] unless parts[:authority].nil?
420 
421   # path requires space to be encoded as %20 (NEVER +)
422   # + should be left unencoded
423   # URI::parse and URI::Generic.build don't like paths encoded with CGI.escape
424   # URI.escape does not change / to %2F and : to %3A like CGI.escape
425   #
426   encoded += rfc2396_escape(parts[:path]) unless parts[:path].nil?
427 
428   # each query parameter
429   if !parts[:query].nil?
430     query_string = parts[:query].split('&').map do |pair|
431       # can optionally be separated by an =
432       pair.split('=').map do |v|
433         uri_query_encode(v)
434       end.join('=')
435     end.join('&')
436     encoded += '?' + query_string
437   end
438 
439   encoded += ((opts[:allow_fragment] ? '#' : '%23') + uri_query_encode(parts[:fragment])) unless parts[:fragment].nil?
440 
441   encoded
442 end
uri_query_encode(query_string) click to toggle source

Percent-encodes a URI query parameter per RFC3986 - tools.ietf.org/html/rfc3986

The output will correctly round-trip through URI.unescape

@param [String query_string] A URI query parameter that may contain reserved

characters that must be percent encoded for the key or value to be
properly decoded as part of a larger query string:

query
encodes as : query

query_with_special=chars like&and * and# plus+this
encodes as:
query_with_special%3Dchars%20like%26and%20%2A%20and%23%20plus%2Bthis

Note: Also usable by fragments, but not suitable for paths

@return [String] a new string containing an encoded query string per the

rules of RFC3986.

In particular,
query will encode + as %2B and space as %20
    # File lib/puppet/util.rb
359 def uri_query_encode(query_string)
360   return nil if query_string.nil?
361 
362   # query can encode space to %20 OR +
363   # + MUST be encoded as %2B
364   # in RFC3968 both query and fragment are defined as:
365   # = *( pchar / "/" / "?" )
366   # CGI.escape turns space into + which is the most backward compatible
367   # however it doesn't roundtrip through URI.unescape which prefers %20
368   CGI.escape(query_string).gsub('+', '%20')
369 end
uri_to_path(uri) click to toggle source

Get the path component of a URI

    # File lib/puppet/util.rb
316 def uri_to_path(uri)
317   return unless uri.is_a?(URI)
318 
319   # CGI.unescape doesn't handle space rules properly in uri paths
320   # URI.unescape does, but returns strings in their original encoding
321   path = uri_unescape(uri.path.encode(Encoding::UTF_8))
322 
323   if Puppet::Util::Platform.windows? && uri.scheme == 'file'
324     if uri.host && !uri.host.empty?
325       path = "//#{uri.host}" + path # UNC
326     else
327       path.sub!(/^\//, '')
328     end
329   end
330 
331   path
332 end
uri_unescape(str) click to toggle source
    # File lib/puppet/util.rb
466 def uri_unescape(str)
467   enc = str.encoding
468   enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
469   str.gsub(ESCAPED) { [$&[1, 2]].pack('H2').force_encoding(enc) }
470 end
which(bin) click to toggle source

Resolve a path for an executable to the absolute path. This tries to behave in the same manner as the unix `which` command and uses the `PATH` environment variable.

@api public @param bin [String] the name of the executable to find. @return [String] the absolute path to the found executable.

    # File lib/puppet/util.rb
207 def which(bin)
208   if absolute_path?(bin)
209     return bin if FileTest.file? bin and FileTest.executable? bin
210   else
211     exts = ENV['PATHEXT']
212     exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD]
213     ENV['PATH'].split(File::PATH_SEPARATOR).each do |dir|
214       begin
215         dest = File.expand_path(File.join(dir, bin))
216       rescue ArgumentError => e
217         # if the user's PATH contains a literal tilde (~) character and HOME is not set, we may get
218         # an ArgumentError here.  Let's check to see if that is the case; if not, re-raise whatever error
219         # was thrown.
220         if e.to_s =~ /HOME/ and (ENV['HOME'].nil? || ENV['HOME'] == "")
221           # if we get here they have a tilde in their PATH.  We'll issue a single warning about this and then
222           # ignore this path element and carry on with our lives.
223           #TRANSLATORS PATH and HOME are environment variables and should not be translated
224           Puppet::Util::Warnings.warnonce(_("PATH contains a ~ character, and HOME is not set; ignoring PATH element '%{dir}'.") % { dir: dir })
225         elsif e.to_s =~ /doesn't exist|can't find user/
226           # ...otherwise, we just skip the non-existent entry, and do nothing.
227           #TRANSLATORS PATH is an environment variable and should not be translated
228           Puppet::Util::Warnings.warnonce(_("Couldn't expand PATH containing a ~ character; ignoring PATH element '%{dir}'.") % { dir: dir })
229         else
230           raise
231         end
232       else
233         if Puppet::Util::Platform.windows? && File.extname(dest).empty?
234           exts.each do |ext|
235             destext = File.expand_path(dest + ext)
236             return destext if FileTest.file? destext and FileTest.executable? destext
237           end
238         end
239         return dest if FileTest.file? dest and FileTest.executable? dest
240       end
241     end
242   end
243   nil
244 end
withenv(hash, mode = :posix) { || ... } click to toggle source

Run some code with a specific environment. Resets the environment back to what it was at the end of the code.

@param hash [Hash{String,Symbol => String}] Environment variables to override the current environment. @param mode [Symbol] ignored

   # File lib/puppet/util.rb
83 def withenv(hash, mode = :posix)
84   saved = ENV.to_hash
85   begin
86     ENV.merge!(hash.transform_keys(&:to_s))
87     yield
88   ensure
89     ENV.replace(saved)
90   end
91 end