class Chef::Provider::Package::Dnf::PythonHelper

Constants

DNF_HELPER

Attributes

inpipe[RW]
outpipe[RW]
stderr[RW]
stdin[RW]
stdout[RW]
wait_thr[RW]

Public Instance Methods

check() click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 81
def check
  start if stdin.nil?
end
compare_versions(version1, version2) click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 85
def compare_versions(version1, version2)
  query("versioncompare", { "versions" => [version1, version2] }).to_i
end
dnf_command() click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 42
def dnf_command
  # platform-python is used for system tools on RHEL 8 and is installed under /usr/libexec
  @dnf_command ||= begin
                     cmd = which("platform-python", "python", "python3", "python2", "python2.7", extra_path: "/usr/libexec") do |f|
                       shell_out("#{f} -c 'import dnf'").exitstatus == 0
                     end
                     raise Chef::Exceptions::Package, "cannot find dnf libraries, you may need to use yum_package" unless cmd

                     "#{cmd} #{DNF_HELPER}"
                   end
end
options_params(options) click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 89
def options_params(options)
  options.each_with_object({}) do |opt, h|
    if opt =~ /--enablerepo=(.+)/
      $1.split(",").each do |repo|
        h["repos"] ||= []
        h["repos"].push( { "enable" => repo } )
      end
    end
    if opt =~ /--disablerepo=(.+)/
      $1.split(",").each do |repo|
        h["repos"] ||= []
        h["repos"].push( { "disable" => repo } )
      end
    end
  end
end
package_query(action, provides, version: nil, arch: nil, options: {}) click to toggle source

@return Array<Version> NB: “options” here is the dnf_package options hash and is deliberately not **opts

# File lib/chef/provider/package/dnf/python_helper.rb, line 108
def package_query(action, provides, version: nil, arch: nil, options: {})
  parameters = { "provides" => provides, "version" => version, "arch" => arch }
  repo_opts = options_params(options || {})
  parameters.merge!(repo_opts)
  # XXX: for now we restart before and after every query with an enablerepo/disablerepo to clean the helpers internal state
  restart unless repo_opts.empty?
  query_output = query(action, parameters)
  version = parse_response(query_output.lines.last)
  Chef::Log.trace "parsed #{version} from python helper"
  reap unless repo_opts.empty?
  version
end
reap() click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 62
def reap
  unless wait_thr.nil?
    Process.kill("INT", wait_thr.pid) rescue nil
    begin
      Timeout.timeout(3) do
        wait_thr.value # this calls waitpid()
      end
    rescue Timeout::Error
      Process.kill("KILL", wait_thr.pid) rescue nil
    end
    stdin.close unless stdin.nil?
    stdout.close unless stdout.nil?
    stderr.close unless stderr.nil?
    inpipe.close unless inpipe.nil?
    outpipe.close unless outpipe.nil?
    @stdin = @stdout = @stderr = @inpipe = @outpipe = @wait_thr = nil
  end
end
restart() click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 121
def restart
  reap
  start
end
start() click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 54
def start
  @inpipe, inpipe_write = IO.pipe
  outpipe_read, @outpipe = IO.pipe
  @stdin, @stdout, @stderr, @wait_thr = Open3.popen3("#{dnf_command} #{outpipe_read.fileno} #{inpipe_write.fileno}", outpipe_read.fileno => outpipe_read, inpipe_write.fileno => inpipe_write, close_others: false)
  outpipe_read.close
  inpipe_write.close
end

Private Instance Methods

add_version(hash, version) click to toggle source

i couldn’t figure out how to decompose an evr on the python side, it seems reasonably painless to do it in ruby (generally massaging nevras in the ruby side is HIGHLY discouraged – this is an “every rule has an exception” exception – any additional functionality should probably trigger moving this regexp logic into python)

# File lib/chef/provider/package/dnf/python_helper.rb, line 132
def add_version(hash, version)
  epoch = nil
  if version =~ /(\S+):(\S+)/
    epoch = $1
    version = $2
  end
  if version =~ /(\S+)-(\S+)/
    version = $1
    release = $2
  end
  hash["epoch"] = epoch unless epoch.nil?
  hash["release"] = release unless release.nil?
  hash["version"] = version
end
build_query(action, parameters) click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 159
def build_query(action, parameters)
  hash = { "action" => action }
  parameters.each do |param_name, param_value|
    hash[param_name] = param_value unless param_value.nil?
  end

  # Special handling for certain action / param combos
  if %i{whatinstalled whatavailable}.include?(action)
    add_version(hash, parameters["version"]) unless parameters["version"].nil?
  end

  FFI_Yajl::Encoder.encode(hash)
end
drain_fds() click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 178
def drain_fds
  output = ""
  fds, = IO.select([stderr, stdout, inpipe], nil, nil, 0)
  unless fds.nil?
    fds.each do |fd|
      output += fd.sysread(4096) rescue ""
    end
  end
  output
rescue => e
  output
end
parse_response(output) click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 173
def parse_response(output)
  array = output.split.map { |x| x == "nil" ? nil : x }
  array.each_slice(3).map { |x| Version.new(*x) }.first
end
query(action, parameters) click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 147
def query(action, parameters)
  with_helper do
    json = build_query(action, parameters)
    Chef::Log.trace "sending '#{json}' to python helper"
    outpipe.puts json
    outpipe.flush
    output = inpipe.readline.chomp
    Chef::Log.trace "got '#{output}' from python helper"
    output
  end
end
with_helper() { || ... } click to toggle source
# File lib/chef/provider/package/dnf/python_helper.rb, line 191
def with_helper
  max_retries ||= 5
  ret = nil
  Timeout.timeout(600) do
    check
    ret = yield
  end
  output = drain_fds
  unless output.empty?
    Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}"
  end
  ret
rescue => e
  output = drain_fds
  restart
  if ( max_retries -= 1 ) > 0 && !ENV["DNF_HELPER_NO_RETRIES"]
    unless output.empty?
      Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}"
    end
    retry
  else
    raise e if output.empty?

    raise "dnf-helper.py had stderr/stdout output:\n\n#{output}"
  end
end