Class: Facter::Core::Execution::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/facter/custom_facts/core/execution/base.rb

Overview

Since:

  • 2.0.0

Direct Known Subclasses

Posix, Windows

Constant Summary collapse

STDERR_MESSAGE =

Since:

  • 2.0.0

'Command %s completed with the following stderr message: %s'
VALID_OPTIONS =

Since:

  • 2.0.0

%i[on_fail expand logger timeout].freeze
DEFAULT_EXECUTION_TIMEOUT =

Since:

  • 2.0.0

300

Instance Method Summary collapse

Constructor Details

#initializeBase

Returns a new instance of Base.

Since:

  • 2.0.0



12
13
14
# File 'lib/facter/custom_facts/core/execution/base.rb', line 12

def initialize
  @log = Log.new(self)
end

Instance Method Details

#execute(command, options = {}) ⇒ Object

Since:

  • 2.0.0



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/facter/custom_facts/core/execution/base.rb', line 45

def execute(command, options = {})
  on_fail, expand, logger, timeout = extract_options(options)

  expanded_command = if !expand && builtin_command?(command) || logger
                       command
                     else
                       expand_command(command)
                     end

  if expanded_command.nil?
    if on_fail == :raise
      raise Facter::Core::Execution::ExecutionFailure.new,
            "Could not execute '#{command}': command not found"
    end

    return on_fail
  end

  out, = execute_command(expanded_command, on_fail, logger, timeout)
  out
end

#execute_command(command, on_fail = nil, logger = nil, timeout = nil) ⇒ Object

Since:

  • 2.0.0



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/facter/custom_facts/core/execution/base.rb', line 67

def execute_command(command, on_fail = nil, logger = nil, timeout = nil)
  timeout ||= DEFAULT_EXECUTION_TIMEOUT
  begin
    # Set LC_ALL and LANG to force i18n to C for the duration of this exec;
    # this ensures that any code that parses the
    # output of the command can expect it to be in a consistent / predictable format / locale
    opts = { 'LC_ALL' => 'C', 'LANG' => 'C' }
    require 'timeout'
    @log.debug("Executing command: #{command}")
    out, stderr = Popen3.popen3e(opts, command.to_s) do |_, stdout, stderr, pid|
      stdout_messages = +''
      stderr_messages = +''
      out_reader = Thread.new { stdout.read }
      err_reader = Thread.new { stderr.read }
      begin
        Timeout.timeout(timeout) do
          stdout_messages << out_reader.value
          stderr_messages << err_reader.value
        end
      rescue Timeout::Error
        message = "Timeout encounter after #{timeout}s, killing process with pid: #{pid}"
        Process.kill('KILL', pid)
        on_fail == :raise ? (raise StandardError, message) : @log.debug(message)
      ensure
        out_reader.kill
        err_reader.kill
      end
      [stdout_messages, stderr_messages]
    end
    log_stderr(stderr, command, logger)
  rescue StandardError => e
    message = "Failed while executing '#{command}': #{e.message}"
    if logger
      @log.debug(message)
      return +''
    end

    return on_fail unless on_fail == :raise

    raise Facter::Core::Execution::ExecutionFailure.new, message
  end

  out.force_encoding(Encoding.default_external) unless out.valid_encoding?
  [out.strip, stderr]
end

#with_env(values) ⇒ Object

This is part of the public API. No race condition can happen here because custom facts are executed sequentially

Since:

  • 2.0.0



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/facter/custom_facts/core/execution/base.rb', line 18

def with_env(values)
  old = {}
  values.each do |var, value|
    # save the old value if it exists
    if (old_val = ENV[var])
      old[var] = old_val
    end
    # set the new (temporary) value for the environment variable
    ENV[var] = value
  end
  # execute the caller's block, capture the return value
  rv = yield
# use an ensure block to make absolutely sure we restore the variables
ensure
  # restore the old values
  values.each_key do |var|
    if old.include?(var)
      ENV[var] = old[var]
    else
      # if there was no old value, delete the key from the current environment variables hash
      ENV.delete(var)
    end
  end
  # return the captured return value
  rv
end