Skip to content

Commit 5e6faed

Browse files
committed
(maint) Add xbps used by voidlinux as a package provider
1 parent d569f6c commit 5e6faed

File tree

3 files changed

+279
-0
lines changed

3 files changed

+279
-0
lines changed

lib/puppet/provider/package/xbps.rb

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
require_relative "../../../puppet/provider/package"
2+
require "set"
3+
require "uri"
4+
5+
Puppet::Type.type(:package).provide :xbps, :parent => Puppet::Provider::Package do
6+
desc "Support for the Package Manager Utility (xbps) used in VoidLinux.
7+
8+
This provider supports the `install_options` attribute, which allows command-line flags to be passed to xbps-install.
9+
These options should be specified as an array where each element is either a string or a hash."
10+
11+
commands :xbps_install => "/usr/bin/xbps-install"
12+
commands :xbps_remove => "/usr/bin/xbps-remove"
13+
commands :xbps_query => "/usr/bin/xbps-query"
14+
commands :xbps_pkgdb => "/usr/bin/xbps-pkgdb"
15+
16+
confine :operatingsystem => [:void]
17+
defaultfor :operatingsystem => [:void]
18+
has_feature :install_options, :uninstall_options, :upgradeable, :holdable, :virtual_packages
19+
20+
def self.defaultto_allow_virtual
21+
false
22+
end
23+
24+
# Fetch the list of packages that are currently installed on the system.
25+
def self.instances
26+
begin
27+
packages = []
28+
execpipe([command(:xbps_query), "-l"]) do |pipe|
29+
# xbps-query -l output is 'ii package-name-version desc'
30+
regex = %r{^\S+\s(\S+)-(\S+)\s+\S+}
31+
pipe.each_line do |line|
32+
match = regex.match(line.chomp)
33+
if match
34+
packages << new({ name: match.captures[0], ensures: match.captures[1], provider: self.name })
35+
else
36+
warning(_("Failed to match line '%{line}'") % { line: line })
37+
end
38+
end
39+
end
40+
41+
packages
42+
rescue Puppet::ExecutionFailure
43+
fail(_("Error getting installed packages"))
44+
end
45+
end
46+
47+
# Install a package quietly (without confirmation or progress bar) using 'xbps-install'.
48+
def install
49+
resource_name = @resource[:name]
50+
resource_source = @resource[:source]
51+
52+
cmd = %w{-S -y}
53+
cmd += install_options if @resource[:install_options]
54+
cmd << "--repository=#{resource_source}" if resource_source
55+
cmd << resource_name
56+
57+
self.unhold if self.properties[:mark] == :hold
58+
begin
59+
xbps_install(*cmd)
60+
ensure
61+
self.hold if @resource[:mark] == :hold
62+
end
63+
end
64+
65+
# Because Voidlinux is a rolling release based distro, installing a package
66+
# should always result in the newest release.
67+
def update
68+
self.install
69+
end
70+
71+
# Removes a package from the system.
72+
def uninstall
73+
resource_name = @resource[:name]
74+
75+
cmd = %w{-R -y}
76+
cmd += uninstall_options if @resource[:uninstall_options]
77+
cmd << resource_name
78+
79+
xbps_remove(*cmd)
80+
end
81+
82+
# The latest version of a given package
83+
def latest
84+
return query&.[] :ensures
85+
end
86+
87+
# Queries information for a package
88+
def query
89+
resource_name = @resource[:name]
90+
installed_packages = self.class.instances
91+
92+
installed_packages.each do |pkg|
93+
return pkg.properties if @resource[:name].casecmp(pkg.name).zero?
94+
end
95+
96+
return nil unless @resource.allow_virtual?
97+
98+
# Search for virtual package
99+
output = xbps_query("-Rs", resource_name).chomp
100+
101+
# xbps-query -Rs output is '[*] package-name-version description'
102+
regex = %r{^\[\*\]+\s(\S+)-(\S+)\s+\S+}
103+
match = regex.match(output)
104+
105+
return nil unless match
106+
return { name: match.captures[0], ensures: match.captures[1], provider: self.class.name }
107+
end
108+
109+
# Puts a package on hold, so it doesn't update by itself on system update
110+
def hold
111+
xbps_pkgdb("-m", "hold", @resource[:name])
112+
end
113+
114+
# Puts a package out of hold
115+
def unhold
116+
xbps_pkgdb("-m", "unhold", @resource[:name])
117+
end
118+
119+
private
120+
121+
def install_options
122+
join_options(@resource[:install_options])
123+
end
124+
125+
def uninstall_options
126+
join_options(@resource[:uninstall_options])
127+
end
128+
end

locales/puppet.pot

+8
Original file line numberDiff line numberDiff line change
@@ -6725,6 +6725,14 @@ msgstr ""
67256725
msgid "Don't know how to install '%{source}'"
67266726
msgstr ""
67276727

6728+
#: ../lib/puppet/provider/package/xbps.rb:36
6729+
msgid "Failed to match line '%{line}"
6730+
msgstr ""
6731+
6732+
#: ../lib/puppet/provider/package/xbps.rb:43
6733+
msgid "Error getting installed packages"
6734+
msgstr ""
6735+
67286736
#: ../lib/puppet/provider/package/yum.rb:70
67296737
msgid "The yum provider can only be used as root"
67306738
msgstr ""
+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
require "spec_helper"
2+
require "stringio"
3+
4+
describe Puppet::Type.type(:package).provider(:xbps) do
5+
before do
6+
@resource = Puppet::Type.type(:package).new(name: "gcc", provider: "xbps")
7+
@provider = described_class.new(@resource)
8+
@resolver = Puppet::Util
9+
10+
allow(@resolver).to receive(:which).with("/usr/bin/xbps-install").and_return("/usr/bin/xbps-install")
11+
allow(@resolver).to receive(:which).with("/usr/bin/xbps-remove").and_return("/usr/bin/xbps-remove")
12+
allow(@resolver).to receive(:which).with("/usr/bin/xbps-query").and_return("/usr/bin/xbps-query")
13+
end
14+
15+
it { is_expected.to be_installable }
16+
it { is_expected.to be_uninstallable }
17+
it { is_expected.to be_install_options }
18+
it { is_expected.to be_uninstall_options }
19+
it { is_expected.to be_upgradeable }
20+
it { is_expected.to be_holdable }
21+
it { is_expected.to be_virtual_packages }
22+
23+
it "should be the default provider on :operatingsystem => Void" do
24+
expect(Facter).to receive(:value).with(:operatingsystem).and_return("Void")
25+
expect(described_class.default?).to be_truthy
26+
end
27+
28+
describe "when determining instances" do
29+
it "should return installed packages" do
30+
sample_installed_packages = %{
31+
ii gcc-12.2.0_1 GNU Compiler Collection
32+
ii ruby-devel-3.1.3_1 Ruby programming language - development files
33+
}
34+
35+
expect(described_class).to receive(:execpipe).with(["/usr/bin/xbps-query", "-l"])
36+
.and_yield(StringIO.new(sample_installed_packages))
37+
38+
instances = described_class.instances
39+
expect(instances.length).to eq(2)
40+
41+
expect(instances[0].properties).to eq({
42+
:name => "gcc",
43+
:ensures => "12.2.0_1",
44+
:provider => :xbps,
45+
})
46+
47+
expect(instances[1].properties).to eq({
48+
:name => "ruby-devel",
49+
:ensures => "3.1.3_1",
50+
:provider => :xbps,
51+
})
52+
end
53+
54+
it "should warn on invalid input" do
55+
expect(described_class).to receive(:execpipe).and_yield(StringIO.new("blah"))
56+
expect(described_class).to receive(:warning).with('Failed to match line \'blah\'')
57+
expect(described_class.instances).to eq([])
58+
end
59+
end
60+
61+
describe "when installing" do
62+
it "and install_options are given it should call xbps to install the package quietly with the passed options" do
63+
@resource[:install_options] = ["-x", { "--arg" => "value" }]
64+
args = ["-S", "-y", "-x", "--arg=value", @resource[:name]]
65+
expect(@provider).to receive(:xbps_install).with(*args).and_return("")
66+
expect(described_class).to receive(:execpipe).with(["/usr/bin/xbps-query", "-l"])
67+
68+
@provider.install
69+
end
70+
71+
it "and source is given it should call xbps to install the package from the source as repository" do
72+
@resource[:source] = "/path/to/xbps/containing/directory"
73+
args = ["-S", "-y", "--repository=#{@resource[:source]}", @resource[:name]]
74+
expect(@provider).to receive(:xbps_install).at_least(:once).with(*args).and_return("")
75+
expect(described_class).to receive(:execpipe).with(["/usr/bin/xbps-query", "-l"])
76+
77+
@provider.install
78+
end
79+
end
80+
81+
describe "when updating" do
82+
it "should call install" do
83+
expect(@provider).to receive(:install).and_return("ran install")
84+
expect(@provider.update).to eq("ran install")
85+
end
86+
end
87+
88+
describe "when uninstalling" do
89+
it "should call xbps to remove the right package quietly" do
90+
args = ["-R", "-y", @resource[:name]]
91+
expect(@provider).to receive(:xbps_remove).with(*args).and_return("")
92+
@provider.uninstall
93+
end
94+
95+
it "adds any uninstall_options" do
96+
@resource[:uninstall_options] = ["-x", { "--arg" => "value" }]
97+
args = ["-R", "-y", "-x", "--arg=value", @resource[:name]]
98+
expect(@provider).to receive(:xbps_remove).with(*args).and_return("")
99+
@provider.uninstall
100+
end
101+
end
102+
103+
describe "when determining the latest version" do
104+
it "should return the latest version number of the package" do
105+
@resource[:name] = "ruby-devel"
106+
107+
expect(described_class).to receive(:execpipe).with(["/usr/bin/xbps-query", "-l"]).and_yield(StringIO.new(%{
108+
ii ruby-devel-3.1.3_1 Ruby programming language - development files
109+
}))
110+
111+
expect(@provider.latest).to eq("3.1.3_1")
112+
end
113+
end
114+
115+
describe "when querying" do
116+
it "should call self.instances and return nil if the package is missing" do
117+
expect(described_class).to receive(:instances)
118+
.and_return([])
119+
120+
expect(@provider.query).to be_nil
121+
end
122+
123+
it "should get real-package in case allow_virtual is true" do
124+
@resource[:name] = "nodejs-runtime"
125+
@resource[:allow_virtual] = true
126+
127+
expect(described_class).to receive(:execpipe).with(["/usr/bin/xbps-query", "-l"])
128+
.and_yield(StringIO.new(""))
129+
130+
args = ["-Rs", @resource[:name]]
131+
expect(@provider).to receive(:xbps_query).with(*args).and_return(%{
132+
[*] nodejs-16.19.0_1 Evented I/O for V8 javascript
133+
[-] nodejs-lts-12.22.10_2 Evented I/O for V8 javascript'
134+
})
135+
136+
expect(@provider.query).to eq({
137+
:name => "nodejs",
138+
:ensures => "16.19.0_1",
139+
:provider => :xbps,
140+
})
141+
end
142+
end
143+
end

0 commit comments

Comments
 (0)