From 9c5deecf8da150ca44b0494573f8c6b0d80d40fa Mon Sep 17 00:00:00 2001 From: Steven Danna Date: Mon, 30 Mar 2015 14:56:27 +0100 Subject: Use the same python interpreter as yum when possible We rely on a system python installation with the correct python libraries. To improve the chances that the python interpreter we call has the yum libary available, we use the same interpreter as yum uses. Fixes #3143 --- lib/chef/provider/package/yum.rb | 30 +++++++++++++++++++++++++++- spec/unit/provider/package/yum_spec.rb | 36 +++++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb index 49c6f6beb5..b3d3d72844 100644 --- a/lib/chef/provider/package/yum.rb +++ b/lib/chef/provider/package/yum.rb @@ -19,6 +19,7 @@ require 'chef/config' require 'chef/provider/package' require 'chef/mixin/shell_out' +require 'chef/mixin/which' require 'chef/resource/package' require 'singleton' require 'chef/mixin/get_source_from_package' @@ -647,6 +648,7 @@ class Chef # Cache for our installed and available packages, pulled in from yum-dump.py class YumCache include Chef::Mixin::Command + include Chef::Mixin::Which include Chef::Mixin::ShellOut include Singleton @@ -713,7 +715,7 @@ class Chef status = nil begin - status = shell_out!("/usr/bin/python #{helper}#{opts}", :timeout => Chef::Config[:yum_timeout]) + status = shell_out!("#{python_bin} #{helper}#{opts}", :timeout => Chef::Config[:yum_timeout]) status.stdout.each_line do |line| one_line = true @@ -779,6 +781,32 @@ class Chef @next_refresh = :none end + def python_bin + yum_executable = which("yum") + if yum_executable && shabang?(yum_executable) + extract_interpreter(yum_executable) + else + Chef::Log.warn("Yum executable not found or doesn't start with #!. Using default python.") + "/usr/bin/python" + end + rescue StandardError => e + Chef::Log.warn("An error occured attempting to determine correct python executable. Using default.") + Chef::Log.debug(e) + "/usr/bin/python" + end + + def extract_interpreter(file) + ::File.open(file, 'r', &:readline)[2..-1].chomp + end + + def shabang?(file) + ::File.open(file, 'r') do |f| + f.read(2) == '#!' + end + rescue Errno::ENOENT + false + end + def reload @next_refresh = :all end diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb index 865dce23fa..8f4bde7f62 100644 --- a/spec/unit/provider/package/yum_spec.rb +++ b/spec/unit/provider/package/yum_spec.rb @@ -17,6 +17,7 @@ # require 'spec_helper' +require 'securerandom' describe Chef::Provider::Package::Yum do before(:each) do @@ -1659,6 +1660,14 @@ describe Chef::Provider::Package::Yum::YumCache do end end + let(:yum_exe) { + StringIO.new("#!/usr/bin/python\n\naldsjfa\ldsajflkdsjf\lajsdfj") + } + + let(:bin_exe) { + StringIO.new(SecureRandom.random_bytes) + } + before(:each) do @stdin = double("STDIN", :nil_object => true) @stdout = double("STDOUT", :nil_object => true) @@ -1704,12 +1713,19 @@ file: file://///etc/yum.repos.d/CentOS-Base.repo, line: 12 'qeqwewe\n' EOF @status = double("Status", :exitstatus => 0, :stdin => @stdin, :stdout => @stdout_good, :stderr => @stderr) - # new singleton each time Chef::Provider::Package::Yum::YumCache.reset_instance @yc = Chef::Provider::Package::Yum::YumCache.instance # load valid data allow(@yc).to receive(:shell_out!).and_return(@status) + allow_any_instance_of(described_class).to receive(:which).with("yum").and_return("/usr/bin/yum") + allow(::File).to receive(:open).with("/usr/bin/yum", "r") do |&block| + res = block.call(yum_exe) + # a bit of a hack. rewind this since it seem that no matter what + # I do, we get the same StringIO objects on multiple calls to + # ::File.open + yum_exe.rewind; res + end end describe "initialize" do @@ -1726,6 +1742,24 @@ EOF end end + describe "python_bin" do + it "should return the default python if an error occurs" do + allow(::File).to receive(:open).with("/usr/bin/yum", "r").and_raise(StandardError) + expect(@yc.python_bin).to eq("/usr/bin/python") + end + + it "should return the default python if the yum-executable doesn't start with #!" do + allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(bin_exe); bin_exe.rewind; r} + expect(@yc.python_bin).to eq("/usr/bin/python") + end + + it "should return the interpreter for yum" do + other = StringIO.new("#!/usr/bin/super_python\n\nlasjdfdsaljf\nlasdjfs") + allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(other); other.rewind; r} + expect(@yc.python_bin).to eq("/usr/bin/super_python") + end + end + describe "refresh" do it "should implicitly call yum-dump.py only once by default after being instantiated" do expect(@yc).to receive(:shell_out!).once -- cgit v1.2.1