summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chef/lib/chef/provider/package/rubygems.rb140
-rw-r--r--chef/spec/unit/provider/package/rubygems_spec.rb145
2 files changed, 203 insertions, 82 deletions
diff --git a/chef/lib/chef/provider/package/rubygems.rb b/chef/lib/chef/provider/package/rubygems.rb
index d9c80f2e33..e9345bf795 100644
--- a/chef/lib/chef/provider/package/rubygems.rb
+++ b/chef/lib/chef/provider/package/rubygems.rb
@@ -6,9 +6,9 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,11 +20,68 @@ require 'chef/provider/package'
require 'chef/mixin/command'
require 'chef/resource/package'
+require 'rubygems/specification'
+
class Chef
class Provider
class Package
- class Rubygems < Chef::Provider::Package
-
+ class Rubygems < Chef::Provider::Package
+ class CurrentGemEnvironment
+
+ def gem_paths
+ Gem.path
+ end
+
+ def installed_versions(gem_name, version_spec=nil)
+ gem_source_index.search(Gem::Dependency.new(gem_name, version_spec)).map { |g| g.version }
+ end
+
+ def gem_source_index
+ Gem.source_index
+ end
+
+ end
+
+ class AlternateGemEnvironment
+ include Chef::Mixin::ShellOut
+
+ attr_reader :gem_binary_location
+
+ def initialize(gem_binary_location)
+ @gem_binary_location = gem_binary_location
+ end
+
+ def gem_paths
+ @gempaths ||= begin
+ # shellout! is a fork/exec which won't work on windows
+ shell_style_paths = shell_out!("#{@gem_binary_location} env gempath").stdout
+ # on windows, the path separator is (usually? always?) semicolon
+ shell_style_paths.split(::File::PATH_SEPARATOR).map { |path| path.strip }
+ end
+ end
+
+ # TODO: de-duplicate
+ def installed_versions(gem_name, version_spec=nil)
+ gem_source_index.search(Gem::Dependency.new(gem_name, version_spec)).map { |g| g.version }
+ end
+
+ def gem_source_index
+ @source_index ||= Gem::SourceIndex.from_gems_in(*gem_paths.map { |p| p + '/specifications' })
+ end
+
+ end
+
+ attr_reader :gem_implementation
+
+ def initialize(new_resource, run_context=nil)
+ super
+ if new_resource.gem_binary
+ @gem_implementation = AlternateGemEnvironment.new(new_resource.gem_binary)
+ else
+ @gem_implementation = CurrentGemEnvironment.new
+ end
+ end
+
def gem_list_parse(line)
installed_versions = Array.new
if md = line.match(/^#{@new_resource.package_name} \((.+?)(?: [^\)\.]+)?\)$/)
@@ -34,38 +91,41 @@ class Chef
end
end
- def gem_binary_path
- path = @new_resource.gem_binary
- path ? path : 'gem'
- end
-
+ # def gem_binary_path
+ # path = @new_resource.gem_binary
+ # path ? path : 'gem'
+ # end
+
def load_current_resource
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
@current_resource.version(nil)
-
- # First, we need to look up whether we have the local gem installed or not
- status = popen4("#{gem_binary_path} list --local #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
- stdout.each_line do |line|
- next unless installed_versions = gem_list_parse(line)
- # If the version we are asking for is installed, make that our current
- # version. Otherwise, go ahead and use the highest one, which
- # happens to come first in the array.
- if installed_versions.detect { |v| v == @new_resource.version }
- Chef::Log.debug("#{@new_resource.package_name} at version #{@new_resource.version}")
- @current_resource.version(@new_resource.version)
- else
- iv = installed_versions.first
- Chef::Log.debug("#{@new_resource.package_name} at version #{iv}")
- @current_resource.version(iv)
- end
- end
- end
-
- unless status.exitstatus == 0
- raise Chef::Exceptions::Package, "#{gem_binary_path} list --local failed - #{status.inspect}!"
- end
+
+ ## Find out if the gem is installed and what the version is
+ pending
+ # First, we need to look up whether we have the local gem installed or not
+ # status = popen4("#{gem_binary_path} list --local #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ # stdout.each_line do |line|
+ # next unless installed_versions = gem_list_parse(line)
+ # # If the version we are asking for is installed, make that our current
+ # # version. Otherwise, go ahead and use the highest one, which
+ # # happens to come first in the array.
+ # if installed_versions.detect { |v| v == @new_resource.version }
+ # Chef::Log.debug("#{@new_resource.package_name} at version #{@new_resource.version}")
+ # @current_resource.version(@new_resource.version)
+ # else
+ # iv = installed_versions.first
+ # Chef::Log.debug("#{@new_resource.package_name} at version #{iv}")
+ # @current_resource.version(iv)
+ # end
+ # end
+ # end
+
+ # unless status.exitstatus == 0
+ # raise Chef::Exceptions::Package, "#{gem_binary_path} list --local failed - #{status.inspect}!"
+ # end
+
@current_resource
end
@@ -76,7 +136,7 @@ class Chef
stdout.each_line do |line|
next unless installed_versions = gem_list_parse(line)
Chef::Log.debug("candidate_version: remote rubygem(s) available: #{installed_versions.inspect}")
-
+
unless installed_versions.empty?
Chef::Log.debug("candidate_version: setting install candidate version to #{installed_versions.first}")
@candidate_version = installed_versions.first
@@ -90,21 +150,21 @@ class Chef
end
@candidate_version
end
-
+
def install_package(name, version)
src = nil
if @new_resource.source
src = " --source=#{@new_resource.source} --source=http://rubygems.org"
- end
+ end
run_command_with_systems_locale(
:command => "#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}"
)
end
-
+
def upgrade_package(name, version)
install_package(name, version)
end
-
+
def remove_package(name, version)
if version
run_command_with_systems_locale(
@@ -116,17 +176,17 @@ class Chef
)
end
end
-
+
def purge_package(name, version)
remove_package(name, version)
end
-
+
private
-
+
def opts
expand_options(@new_resource.options)
end
-
+
end
end
end
diff --git a/chef/spec/unit/provider/package/rubygems_spec.rb b/chef/spec/unit/provider/package/rubygems_spec.rb
index 0400615323..31681a820a 100644
--- a/chef/spec/unit/provider/package/rubygems_spec.rb
+++ b/chef/spec/unit/provider/package/rubygems_spec.rb
@@ -16,62 +16,123 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+require 'pp'
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
+require 'ostruct'
-describe Chef::Provider::Package::Rubygems do
- before(:each) do
- @node = Chef::Node.new
- @run_context = Chef::RunContext.new(@node, {})
- @new_resource = Chef::Resource::GemPackage.new("nokogiri")
- @new_resource.version "1.4.1"
- @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+class String
+ undef_method :version
+end
+
+describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do
+ before do
+ @gem_env = Chef::Provider::Package::Rubygems::CurrentGemEnvironment.new
end
- describe "when selecting the gem binary to use" do
- it "should return a relative path to gem if no gem_binary is given" do
- @provider.gem_binary_path.should == "gem"
- end
+ it "determines the gem paths from the in memory rubygems" do
+ @gem_env.gem_paths.should == Gem.path
+ end
- it "should return a specific path to gem if a gem_binary is given" do
- @new_resource.gem_binary "/opt/local/bin/custom/ruby"
- @provider.gem_binary_path.should == "/opt/local/bin/custom/ruby"
- end
+ it "determines the installed versions of gems from Gem.source_index" do
+ gems = [Gem::Specification.new('rspec', Gem::Version.new('1.2.9')), Gem::Specification.new('rspec', Gem::Version.new('1.3.0'))]
+ Gem.source_index.should_receive(:search).with(Gem::Dependency.new('rspec', nil)).and_return(gems)
+ @gem_env.installed_versions('rspec').should == [Gem::Version.new('1.2.9'), Gem::Version.new('1.3.0')]
end
- describe "loading the current state" do
- it "determines the installed versions of gems" do
- gem_list = "nokogiri (2.3.5, 2.2.2, 1.2.6)"
- @provider.gem_list_parse(gem_list).should == %w{2.3.5 2.2.2 1.2.6}
- end
+ it "determines the installed versions of gems from the source index (part2: the unmockening)" do
+ @gem_env.installed_versions('rspec').should include(Gem::Version.new(Spec::VERSION::STRING))
end
- describe "determining the candidate version" do
- it "parses the available versions as reported by rubygems 1.3.6 and lower" do
- gem_list = "nokogiri (1.4.1)\nnokogiri-happymapper (0.3.3)"
- @provider.gem_list_parse(gem_list).should == ['1.4.1']
- end
+end
- it "parses the available versions as reported by rubygems 1.3.7 and newer" do
- gem_list = "nokogiri (1.4.1 ruby java x86-mingw32 x86-mswin32)\nnokogiri-happymapper (0.3.3)\n"
- @provider.gem_list_parse(gem_list).should == ['1.4.1']
- end
+describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do
+ before do
+ @gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new('/usr/weird/bin/gem')
+ end
+ it "determines the gem paths from shelling out to gem env" do
+ gem_env_output = ['/path/to/gems', '/another/path/to/gems'].join(File::PATH_SEPARATOR)
+ shell_out_result = OpenStruct.new(:stdout => gem_env_output)
+ @gem_env.should_receive(:shell_out!).with('/usr/weird/bin/gem env gempath').and_return(shell_out_result)
+ @gem_env.gem_paths.should == ['/path/to/gems', '/another/path/to/gems']
+ end
+
+ it "builds the gems source index from the gem paths" do
+ Gem::SourceIndex.should_receive(:from_gems_in).with('/path/to/gems/specifications', '/another/path/to/gems/specifications')
+ @gem_env.stub!(:gem_paths).and_return(['/path/to/gems', '/another/path/to/gems'])
+ @gem_env.gem_source_index
+ end
+
+ it "determines the installed versions of gems from the source index" do
+ gems = [Gem::Specification.new('rspec', Gem::Version.new('1.2.9')), Gem::Specification.new('rspec', Gem::Version.new('1.3.0'))]
+ @gem_env.stub!(:gem_source_index).and_return(Gem.source_index)
+ @gem_env.gem_source_index.should_receive(:search).with(Gem::Dependency.new('rspec', nil)).and_return(gems)
+ @gem_env.installed_versions('rspec').should == [Gem::Version.new('1.2.9'), Gem::Version.new('1.3.0')]
end
- describe "when installing a gem" do
- it "should run gem install with the package name and version" do
- @provider.should_receive(:run_command).with(
- :command => "gem install rspec -q --no-rdoc --no-ri -v \"1.2.2\"",
- :environment => {"LC_ALL" => nil})
- @provider.install_package("rspec", "1.2.2")
- end
+ it "determines the installed versions of gems from the source index (part2: the unmockening)" do
+ path_to_gem = `which gem`.strip
+ pending("cant find your gem executable") if path_to_gem.empty?
+ gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new(path_to_gem)
+ gem_env.installed_versions('rspec').should include(Gem::Version.new(Spec::VERSION::STRING))
+ end
- it "installs gems with arbitrary options set by resource's options" do
- @new_resource.options "-i /arbitrary/install/dir"
- @provider.should_receive(:run_command_with_systems_locale).
- with(:command => "gem install rspec -q --no-rdoc --no-ri -v \"1.2.2\" -i /arbitrary/install/dir")
- @provider.install_package("rspec", "1.2.2")
- end
+end
+
+describe Chef::Provider::Package::Rubygems do
+ before(:each) do
+ @node = Chef::Node.new
+ @new_resource = Chef::Resource::GemPackage.new("nokogiri")
+ @new_resource.version "1.4.1"
+ @run_context = Chef::RunContext.new(@node, {})
+
+ @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ end
+
+ it "uses the CurrentGemEnvironment implementation when no gem_binary_path is provided" do
+ @provider.gem_implementation.should be_a_kind_of(Chef::Provider::Package::Rubygems::CurrentGemEnvironment)
end
+
+ it "uses the AlternateGemEnvironment implementation when a gem_binary_path is provided" do
+ @new_resource.gem_binary('/usr/weird/bin/gem')
+ provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ provider.gem_implementation.gem_binary_location.should == '/usr/weird/bin/gem'
+ end
+
+ # describe "loading the current state" do
+ # it "determines the installed versions of gems" do
+ # gem_list = "nokogiri (2.3.5, 2.2.2, 1.2.6)"
+ # @provider.gem_list_parse(gem_list).should == %w{2.3.5 2.2.2 1.2.6}
+ # end
+ # end
+ #
+ # describe "determining the candidate version" do
+ # it "parses the available versions as reported by rubygems 1.3.6 and lower" do
+ # gem_list = "nokogiri (1.4.1)\nnokogiri-happymapper (0.3.3)"
+ # @provider.gem_list_parse(gem_list).should == ['1.4.1']
+ # end
+ #
+ # it "parses the available versions as reported by rubygems 1.3.7 and newer" do
+ # gem_list = "nokogiri (1.4.1 ruby java x86-mingw32 x86-mswin32)\nnokogiri-happymapper (0.3.3)\n"
+ # @provider.gem_list_parse(gem_list).should == ['1.4.1']
+ # end
+ #
+ # end
+ #
+ # describe "when installing a gem" do
+ # it "should run gem install with the package name and version" do
+ # @provider.should_receive(:run_command).with(
+ # :command => "gem install rspec -q --no-rdoc --no-ri -v \"1.2.2\"",
+ # :environment => {"LC_ALL" => nil})
+ # @provider.install_package("rspec", "1.2.2")
+ # end
+ #
+ # it "installs gems with arbitrary options set by resource's options" do
+ # @new_resource.options "-i /arbitrary/install/dir"
+ # @provider.should_receive(:run_command_with_systems_locale).
+ # with(:command => "gem install rspec -q --no-rdoc --no-ri -v \"1.2.2\" -i /arbitrary/install/dir")
+ # @provider.install_package("rspec", "1.2.2")
+ # end
+ # end
end