Skip to content

Commit c8f5222

Browse files
committed
(maint) Add xbps used by voidlinux as a package provider
1 parent 1a53bf7 commit c8f5222

File tree

3 files changed

+278
-0
lines changed

3 files changed

+278
-0
lines changed

lib/puppet/provider/package/xbps.rb

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "../../../puppet/provider/package"
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 'os.name' => :void
17+
defaultfor 'os.name' => :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+
packages = []
27+
execpipe([command(:xbps_query), "-l"]) do |pipe|
28+
# xbps-query -l output is 'ii package-name-version desc'
29+
regex = /^\S+\s(\S+)-(\S+)\s+\S+/
30+
pipe.each_line do |line|
31+
match = regex.match(line.chomp)
32+
if match
33+
packages << new({ name: match.captures[0], ensures: match.captures[1], provider: name })
34+
else
35+
warning(_("Failed to match line '%{line}'") % { line: line })
36+
end
37+
end
38+
end
39+
40+
packages
41+
rescue Puppet::ExecutionFailure
42+
fail(_("Error getting installed packages"))
43+
end
44+
45+
# Install a package quietly (without confirmation or progress bar) using 'xbps-install'.
46+
def install
47+
resource_name = @resource[:name]
48+
resource_source = @resource[:source]
49+
50+
cmd = %w[-S -y]
51+
cmd += install_options if @resource[:install_options]
52+
cmd << "--repository=#{resource_source}" if resource_source
53+
cmd << resource_name
54+
55+
unhold if properties[:mark] == :hold
56+
begin
57+
xbps_install(*cmd)
58+
ensure
59+
hold if @resource[:mark] == :hold
60+
end
61+
end
62+
63+
# Because Voidlinux is a rolling release based distro, installing a package
64+
# should always result in the newest release.
65+
def update
66+
install
67+
end
68+
69+
# Removes a package from the system.
70+
def uninstall
71+
resource_name = @resource[:name]
72+
73+
cmd = %w[-R -y]
74+
cmd += uninstall_options if @resource[:uninstall_options]
75+
cmd << resource_name
76+
77+
xbps_remove(*cmd)
78+
end
79+
80+
# The latest version of a given package
81+
def latest
82+
query&.[] :ensures
83+
end
84+
85+
# Queries information for a package
86+
def query
87+
resource_name = @resource[:name]
88+
installed_packages = self.class.instances
89+
90+
installed_packages.each do |pkg|
91+
return pkg.properties if @resource[:name].casecmp(pkg.name).zero?
92+
end
93+
94+
return nil unless @resource.allow_virtual?
95+
96+
# Search for virtual package
97+
output = xbps_query("-Rs", resource_name).chomp
98+
99+
# xbps-query -Rs output is '[*] package-name-version description'
100+
regex = /^\[\*\]+\s(\S+)-(\S+)\s+\S+/
101+
match = regex.match(output)
102+
103+
return nil unless match
104+
105+
{ name: match.captures[0], ensures: match.captures[1], provider: self.class.name }
106+
end
107+
108+
# Puts a package on hold, so it doesn't update by itself on system update
109+
def hold
110+
xbps_pkgdb("-m", "hold", @resource[:name])
111+
end
112+
113+
# Puts a package out of hold
114+
def unhold
115+
xbps_pkgdb("-m", "unhold", @resource[:name])
116+
end
117+
118+
private
119+
120+
def install_options
121+
join_options(@resource[:install_options])
122+
end
123+
124+
def uninstall_options
125+
join_options(@resource[:uninstall_options])
126+
end
127+
end

locales/puppet.pot

+8
Original file line numberDiff line numberDiff line change
@@ -6741,6 +6741,14 @@ msgstr ""
67416741
msgid "Don't know how to install '%{source}'"
67426742
msgstr ""
67436743

6744+
#: ../lib/puppet/provider/package/xbps.rb:36
6745+
msgid "Failed to match line '%{line}"
6746+
msgstr ""
6747+
6748+
#: ../lib/puppet/provider/package/xbps.rb:43
6749+
msgid "Error getting installed packages"
6750+
msgstr ""
6751+
67446752
#: ../lib/puppet/provider/package/yum.rb:75
67456753
msgid "The yum provider can only be used as root"
67466754
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(described_class).to receive(:which).with("/usr/bin/xbps-install").and_return("/usr/bin/xbps-install")
11+
allow(described_class).to receive(:which).with("/usr/bin/xbps-remove").and_return("/usr/bin/xbps-remove")
12+
allow(described_class).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 'os.name' => Void" do
24+
expect(Facter).to receive(:value).with('os.name').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)