Skip to content

Commit 7dc80dc

Browse files
committed
Merge branch 'CHEF-1575'
2 parents 4744d2a + 83f2ce7 commit 7dc80dc

2 files changed

Lines changed: 205 additions & 65 deletions

File tree

chef/lib/chef/provider/package/portage.rb

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
88
# You may obtain a copy of the License at
9-
#
9+
#
1010
# http://www.apache.org/licenses/LICENSE-2.0
11-
#
11+
#
1212
# Unless required by applicable law or agreed to in writing, software
1313
# distributed under the License is distributed on an "AS IS" BASIS,
1414
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -24,48 +24,59 @@ class Chef
2424
class Provider
2525
class Package
2626
class Portage < Chef::Provider::Package
27-
27+
PACKAGE_NAME_PATTERN = %r{(([^/]+)/)?([^/]+)}
28+
2829
def load_current_resource
2930
@current_resource = Chef::Resource::Package.new(@new_resource.name)
3031
@current_resource.package_name(@new_resource.package_name)
3132

32-
category = @new_resource.package_name.split('/').first
33-
pkg = @new_resource.package_name.split('/').last
34-
3533
@current_resource.version(nil)
3634

37-
catdir = "/var/db/pkg/#{category}"
35+
_, category_with_slash, category, pkg = %r{^#{PACKAGE_NAME_PATTERN}$}.match(@new_resource.package_name).to_a
3836

39-
if( ::File.exists?(catdir) )
40-
Dir.entries(catdir).each do |entry|
41-
if(entry =~ /^#{Regexp.escape(pkg)}\-(\d[\.\d]*((_(alpha|beta|pre|rc|p)\d*)*)?(-r\d+)?)/)
42-
@current_resource.version($1)
43-
Chef::Log.debug("Got current version #{$1}")
44-
break
45-
end
37+
possibilities = Dir["/var/db/pkg/#{category || "*"}/#{pkg}-*"].map {|d| d.sub(%r{/var/db/pkg/}, "") }
38+
versions = possibilities.map do |entry|
39+
if(entry =~ %r{[^/]+/#{Regexp.escape(pkg)}\-(\d[\.\d]*((_(alpha|beta|pre|rc|p)\d*)*)?(-r\d+)?)})
40+
[$&, $1]
4641
end
42+
end.compact
43+
44+
if versions.size > 1
45+
atoms = versions.map {|v| v.first }.sort
46+
raise Chef::Exceptions::Package, "Multiple packages found for #{@new_resource.package_name}: #{atoms.join(" ")}. Specify a category."
47+
elsif versions.size == 1
48+
@current_resource.version(versions.first.last)
49+
Chef::Log.debug("Got current version #{$1}")
4750
end
4851

4952
@current_resource
5053
end
51-
52-
54+
55+
5356
def parse_emerge(package, txt)
54-
available, installed, pkg = nil
55-
txt.each do |line|
56-
if line =~ /\*(.*)/
57-
pkg = $1.strip
57+
availables = {}
58+
package_without_category = package.split("/").last
59+
found_package_name = nil
60+
61+
txt.each_line do |line|
62+
if line =~ /\*\s+#{PACKAGE_NAME_PATTERN}/
63+
found_package_name = $&.strip
64+
if found_package_name == package || found_package_name.split("/").last == package_without_category
65+
availables[found_package_name] = nil
66+
end
5867
end
59-
if (pkg == package) || (pkg.split('/').last == package rescue false)
60-
if line =~ /Latest version available: (.*)/
61-
available = $1
62-
elsif line =~ /Latest version installed: (.*)/
63-
installed = $1
64-
end
68+
69+
if line =~ /Latest version available: (.*)/ && availables.has_key?(found_package_name)
70+
availables[found_package_name] = $1.strip
6571
end
66-
end
67-
available = installed unless available
68-
[available, installed]
72+
end
73+
74+
if availables.size > 1
75+
# shouldn't happen if a category is specified so just use `package`
76+
raise Chef::Exceptions::Package, "Multiple emerge results found for #{package}: #{availables.keys.join(" ")}. Specify a category."
77+
end
78+
79+
availables.values.first
6980
end
7081

7182
def candidate_version
@@ -83,41 +94,41 @@ def candidate_version
8394
@candidate_version
8495

8596
end
86-
87-
97+
98+
8899
def install_package(name, version)
89-
pkg = "=#{name}-#{version}"
90-
100+
pkg = "=#{name}-#{version}"
101+
91102
if(version =~ /^\~(.+)/)
92103
# If we start with a tilde
93104
pkg = "~#{name}-#{$1}"
94105
end
95-
106+
96107
run_command_with_systems_locale(
97108
:command => "emerge -g --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}"
98109
)
99110
end
100-
111+
101112
def upgrade_package(name, version)
102113
install_package(name, version)
103114
end
104-
115+
105116
def remove_package(name, version)
106117
if(version)
107118
pkg = "=#{@new_resource.package_name}-#{version}"
108-
else
119+
else
109120
pkg = "#{@new_resource.package_name}"
110121
end
111122

112123
run_command_with_systems_locale(
113124
:command => "emerge --unmerge --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}"
114125
)
115126
end
116-
127+
117128
def purge_package(name, version)
118129
remove_package(name, version)
119130
end
120-
131+
121132
end
122133
end
123134
end

chef/spec/unit/provider/package/portage_spec.rb

Lines changed: 155 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
88
# You may obtain a copy of the License at
9-
#
9+
#
1010
# http://www.apache.org/licenses/LICENSE-2.0
11-
#
11+
#
1212
# Unless required by applicable law or agreed to in writing, software
1313
# distributed under the License is distributed on an "AS IS" BASIS,
1414
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,70 +22,199 @@
2222
@node = Chef::Node.new
2323
@run_context = Chef::RunContext.new(@node, {})
2424
@new_resource = Chef::Resource::Package.new("dev-util/git")
25+
@new_resource_without_category = Chef::Resource::Package.new("git")
2526
@current_resource = Chef::Resource::Package.new("dev-util/git")
26-
27+
2728
@provider = Chef::Provider::Package::Portage.new(@new_resource, @run_context)
2829
Chef::Resource::Package.stub!(:new).and_return(@current_resource)
29-
30-
::File.stub!(:exists?).and_return(true)
3130
end
32-
31+
3332
describe "when determining the current state of the package" do
34-
33+
3534
it "should create a current resource with the name of new_resource" do
36-
::Dir.stub!(:entries).and_return(["git-1.0.0"])
35+
::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0"])
3736
Chef::Resource::Package.should_receive(:new).and_return(@current_resource)
3837
@provider.load_current_resource
3938
end
40-
39+
4140
it "should set the current resource package name to the new resource package name" do
42-
::Dir.stub!(:entries).and_return(["git-1.0.0"])
41+
::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0"])
4342
@current_resource.should_receive(:package_name).with(@new_resource.package_name)
4443
@provider.load_current_resource
4544
end
46-
45+
4746
it "should return a current resource with the correct version if the package is found" do
48-
::Dir.stub!(:entries).and_return(["git-foobar-0.9", "git-1.0.0"])
47+
::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-foobar-0.9", "/var/db/pkg/dev-util/git-1.0.0"])
4948
@provider.load_current_resource
5049
@provider.current_resource.version.should == "1.0.0"
5150
end
5251

5352
it "should return a current resource with the correct version if the package is found with revision" do
54-
::Dir.stub!(:entries).and_return(["git-1.0.0-r1"])
53+
::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0-r1"])
5554
@provider.load_current_resource
5655
@provider.current_resource.version.should == "1.0.0-r1"
5756
end
58-
57+
5958
it "should return a current resource with a nil version if the package is not found" do
60-
::Dir.stub!(:entries).and_return(["notgit-1.0.0"])
59+
::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/notgit-1.0.0"])
6160
@provider.load_current_resource
6261
@provider.current_resource.version.should be_nil
6362
end
6463

64+
it "should return a package name match from /var/db/pkg/* if a category isn't specified and a match is found" do
65+
::Dir.stub!(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/git-foobar-0.9", "/var/db/pkg/dev-util/git-1.0.0"])
66+
@provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
67+
@provider.load_current_resource
68+
@provider.current_resource.version.should == "1.0.0"
69+
end
70+
71+
it "should return a current resource with a nil version if a category isn't specified and a name match from /var/db/pkg/* is not found" do
72+
::Dir.stub!(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/notgit-1.0.0"])
73+
@provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
74+
@provider.load_current_resource
75+
@provider.current_resource.version.should be_nil
76+
end
77+
78+
it "should throw an exception if a category isn't specified and multiple packages are found" do
79+
::Dir.stub!(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0", "/var/db/pkg/funny-words/git-1.0.0"])
80+
@provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
81+
lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
82+
end
6583
end
66-
84+
6785
describe "once the state of the package is known" do
6886

6987
describe Chef::Provider::Package::Portage, "candidate_version" do
70-
it "should return the candidate_version variable if already setup" do
88+
it "should return the candidate_version variable if already set" do
7189
@provider.candidate_version = "1.0.0"
7290
@provider.should_not_receive(:popen4)
7391
@provider.candidate_version
7492
end
7593

76-
it "should lookup the candidate_version if the variable is not already set" do
77-
@status = mock("Status", :exitstatus => 0)
94+
it "should throw an exception if the exitstatus is not 0" do
95+
@status = mock("Status", :exitstatus => 1)
7896
@provider.stub!(:popen4).and_return(@status)
79-
@provider.should_receive(:popen4)
80-
@provider.candidate_version
97+
lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package)
8198
end
8299

83-
it "should throw and exception if the exitstatus is not 0" do
84-
@status = mock("Status", :exitstatus => 1)
85-
@provider.stub!(:popen4).and_return(@status)
100+
it "should find the candidate_version if a category is specifed and there are no duplicates" do
101+
output = <<EOF
102+
Searching...
103+
[ Results for search key : git ]
104+
[ Applications found : 14 ]
105+
106+
* app-misc/digitemp [ Masked ]
107+
Latest version available: 3.5.0
108+
Latest version installed: [ Not Installed ]
109+
Size of files: 261 kB
110+
Homepage: http://www.digitemp.com/ http://www.ibutton.com/
111+
Description: Temperature logging and reporting using Dallas Semiconductor's iButtons and 1-Wire protocol
112+
License: GPL-2
113+
114+
* dev-util/git
115+
Latest version available: 1.6.0.6
116+
Latest version installed: ignore
117+
Size of files: 2,725 kB
118+
Homepage: http://git.or.cz/
119+
Description: GIT - the stupid content tracker, the revision control system heavily used by the Linux kernel team
120+
License: GPL-2
121+
122+
* dev-util/gitosis [ Masked ]
123+
Latest version available: 0.2_p20080825
124+
Latest version installed: [ Not Installed ]
125+
Size of files: 31 kB
126+
Homepage: http://eagain.net/gitweb/?p=gitosis.git;a=summary
127+
Description: gitosis -- software for hosting git repositories
128+
License: GPL-2
129+
EOF
130+
131+
@status = mock("Status", :exitstatus => 0)
132+
@provider.should_receive(:popen4).and_yield(nil, nil, StringIO.new(output), nil).and_return(@status)
133+
@provider.candidate_version.should == "1.6.0.6"
134+
end
135+
136+
it "should find the candidate_version if a category is not specifed and there are no duplicates" do
137+
output = <<EOF
138+
Searching...
139+
[ Results for search key : git ]
140+
[ Applications found : 14 ]
141+
142+
* app-misc/digitemp [ Masked ]
143+
Latest version available: 3.5.0
144+
Latest version installed: [ Not Installed ]
145+
Size of files: 261 kB
146+
Homepage: http://www.digitemp.com/ http://www.ibutton.com/
147+
Description: Temperature logging and reporting using Dallas Semiconductor's iButtons and 1-Wire protocol
148+
License: GPL-2
149+
150+
* dev-util/git
151+
Latest version available: 1.6.0.6
152+
Latest version installed: ignore
153+
Size of files: 2,725 kB
154+
Homepage: http://git.or.cz/
155+
Description: GIT - the stupid content tracker, the revision control system heavily used by the Linux kernel team
156+
License: GPL-2
157+
158+
* dev-util/gitosis [ Masked ]
159+
Latest version available: 0.2_p20080825
160+
Latest version installed: [ Not Installed ]
161+
Size of files: 31 kB
162+
Homepage: http://eagain.net/gitweb/?p=gitosis.git;a=summary
163+
Description: gitosis -- software for hosting git repositories
164+
License: GPL-2
165+
EOF
166+
167+
@status = mock("Status", :exitstatus => 0)
168+
@provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
169+
@provider.should_receive(:popen4).and_yield(nil, nil, StringIO.new(output), nil).and_return(@status)
170+
@provider.candidate_version.should == "1.6.0.6"
171+
end
172+
173+
it "should throw an exception if a category is not specified and there are duplicates" do
174+
output = <<EOF
175+
Searching...
176+
[ Results for search key : git ]
177+
[ Applications found : 14 ]
178+
179+
* app-misc/digitemp [ Masked ]
180+
Latest version available: 3.5.0
181+
Latest version installed: [ Not Installed ]
182+
Size of files: 261 kB
183+
Homepage: http://www.digitemp.com/ http://www.ibutton.com/
184+
Description: Temperature logging and reporting using Dallas Semiconductor's iButtons and 1-Wire protocol
185+
License: GPL-2
186+
187+
* app-misc/git
188+
Latest version available: 4.3.20
189+
Latest version installed: [ Not Installed ]
190+
Size of files: 416 kB
191+
Homepage: http://www.gnu.org/software/git/
192+
Description: GNU Interactive Tools - increase speed and efficiency of most daily task
193+
License: GPL-2
194+
195+
* dev-util/git
196+
Latest version available: 1.6.0.6
197+
Latest version installed: ignore
198+
Size of files: 2,725 kB
199+
Homepage: http://git.or.cz/
200+
Description: GIT - the stupid content tracker, the revision control system heavily used by the Linux kernel team
201+
License: GPL-2
202+
203+
* dev-util/gitosis [ Masked ]
204+
Latest version available: 0.2_p20080825
205+
Latest version installed: [ Not Installed ]
206+
Size of files: 31 kB
207+
Homepage: http://eagain.net/gitweb/?p=gitosis.git;a=summary
208+
Description: gitosis -- software for hosting git repositories
209+
License: GPL-2
210+
EOF
211+
212+
@status = mock("Status", :exitstatus => 0)
213+
@provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
214+
@provider.should_receive(:popen4).and_yield(nil, nil, StringIO.new(output), nil).and_return(@status)
86215
lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package)
87216
end
88-
217+
89218
end
90219

91220
describe Chef::Provider::Package::Portage, "install_package" do
@@ -108,7 +237,7 @@
108237
:command => "emerge -g --color n --nospinner --quiet --oneshot =dev-util/git-1.0.0"
109238
})
110239
@new_resource.stub!(:options).and_return("--oneshot")
111-
240+
112241
@provider.install_package("dev-util/git", "1.0.0")
113242
end
114243
end

0 commit comments

Comments
 (0)