1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
|
require 'puppet/provider/package'
require 'set'
require 'uri'
Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Package do
desc "Support for the Package Manager Utility (pacman) used in Archlinux.
This provider supports the `install_options` attribute, which allows command-line flags to be passed to pacman.
These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
or an array where each element is either a string or a hash."
# If aura is installed, we can make use of it
def self.aura?
@aura ||= Puppet::FileSystem.exist?('/usr/bin/aura')
end
commands :pacman => "/usr/bin/pacman"
# Aura is a common AUR helper which, if installed, we can use to query the AUR
commands :aura => "/usr/bin/aura" if aura?
confine :operatingsystem => [:archlinux, :manjarolinux]
defaultfor :operatingsystem => [:archlinux, :manjarolinux]
has_feature :install_options
has_feature :uninstall_options
has_feature :upgradeable
has_feature :virtual_packages
# Checks if a given name is a group
def self.group?(name)
begin
!pacman("-Sg", name).empty?
rescue Puppet::ExecutionFailure
# pacman returns an expected non-zero exit code when the name is not a group
false
end
end
# Install a package using 'pacman', or 'aura' if available.
# Installs quietly, without confirmation or progress bar, updates package
# list from servers defined in pacman.conf.
def install
if @resource[:source]
install_from_file
else
install_from_repo
end
unless self.query
fail(_("Could not find package '%{name}'") % { name: @resource[:name] })
end
end
# Fetch the list of packages and package groups that are currently installed on the system.
# Only package groups that are fully installed are included. If a group adds packages over time, it will not
# be considered as fully installed any more, and we would install the new packages on the next run.
# If a group removes packages over time, nothing will happen. This is intended.
def self.instances
instances = []
# Get the installed packages
installed_packages = get_installed_packages
installed_packages.sort_by { |k, _| k }.each do |package, version|
instances << new(to_resource_hash(package, version))
end
# Get the installed groups
get_installed_groups(installed_packages).each do |group, version|
instances << new(to_resource_hash(group, version))
end
instances
end
# returns a hash package => version of installed packages
def self.get_installed_packages
begin
packages = {}
execpipe([command(:pacman), "-Q"]) do |pipe|
# pacman -Q output is 'packagename version-rel'
regex = %r{^(\S+)\s(\S+)}
pipe.each_line do |line|
if match = regex.match(line)
packages[match.captures[0]] = match.captures[1]
else
warning(_("Failed to match line '%{line}'") % { line: line })
end
end
end
packages
rescue Puppet::ExecutionFailure
fail(_("Error getting installed packages"))
end
end
# returns a hash of group => version of installed groups
def self.get_installed_groups(installed_packages, filter = nil)
groups = {}
begin
# Build a hash of group name => list of packages
command = [command(:pacman), "-Sgg"]
command << filter if filter
execpipe(command) do |pipe|
pipe.each_line do |line|
name, package = line.split
packages = (groups[name] ||= [])
packages << package
end
end
# Remove any group that doesn't have all its packages installed
groups.delete_if do |_, packages|
!packages.all? { |package| installed_packages[package] }
end
# Replace the list of packages with a version string consisting of packages that make up the group
groups.each do |name, packages|
groups[name] = packages.sort.map {|package| "#{package} #{installed_packages[package]}"}.join ', '
end
rescue Puppet::ExecutionFailure
# pacman returns an expected non-zero exit code when the filter name is not a group
raise unless filter
end
groups
end
# Because Archlinux is a rolling release based distro, installing a package
# should always result in the newest release.
def update
# Install in pacman can be used for update, too
self.install
end
# We rescue the main check from Pacman with a check on the AUR using aura, if installed
def latest
# Synchronize the database
pacman "-Sy"
resource_name = @resource[:name]
# If target is a group, construct the group version
return pacman("-Sp", "--print-format", "%n %v", resource_name).lines.map{ |line| line.chomp }.sort.join(', ') if self.class.group?(resource_name)
# Start by querying with pacman first
# If that fails, retry using aura against the AUR
pacman_check = true
begin
if pacman_check
output = pacman "-Sp", "--print-format", "%v", resource_name
return output.chomp
else
output = aura "-Ai", resource_name
output.split("\n").each do |line|
return line.split[2].chomp if line.split[0] =~ /Version/
end
end
rescue Puppet::ExecutionFailure
if pacman_check and self.class.aura?
pacman_check = false # now try the AUR
retry
else
raise
end
end
end
# Queries information for a package or package group
def query
installed_packages = self.class.get_installed_packages
resource_name = @resource[:name]
# Check for the resource being a group
version = self.class.get_installed_groups(installed_packages, resource_name)[resource_name]
if version
unless @resource.allow_virtual?
warning(_("%{resource_name} is a group, but allow_virtual is false.") % { resource_name: resource_name })
return nil
end
else
version = installed_packages[resource_name]
end
# Return nil if no package or group found
return nil unless version
self.class.to_resource_hash(resource_name, version)
end
def self.to_resource_hash(name, version)
{
:name => name,
:ensure => version,
:provider => self.name
}
end
# Removes a package from the system.
def uninstall
resource_name = @resource[:name]
is_group = self.class.group?(resource_name)
fail(_("Refusing to uninstall package group %{resource_name}, because allow_virtual is false.") % { resource_name: resource_name }) if is_group && !@resource.allow_virtual?
cmd = %w{--noconfirm --noprogressbar}
cmd += uninstall_options if @resource[:uninstall_options]
cmd << "-R"
cmd << '-s' if is_group
cmd << resource_name
if self.class.aura?
aura(*cmd)
else
pacman(*cmd)
end
end
private
def install_with_aura?
resource_name = @resource[:name]
if !self.class.aura?
return false
end
begin
pacman "-Sp", resource_name
return false
rescue Puppet::ExecutionFailure
return true
end
end
def install_options
join_options(@resource[:install_options])
end
def uninstall_options
join_options(@resource[:uninstall_options])
end
def install_from_file
source = @resource[:source]
begin
source_uri = URI.parse source
rescue => detail
self.fail Puppet::Error, _("Invalid source '%{source}': %{detail}") % { source: source, detail: detail }, detail
end
source = case source_uri.scheme
when nil then source
when /https?/i then source
when /ftp/i then source
when /file/i then source_uri.path
when /puppet/i
fail _("puppet:// URL is not supported by pacman")
else
fail _("Source %{source} is not supported by pacman") % { source: source }
end
pacman "--noconfirm", "--noprogressbar", "-Sy"
pacman "--noconfirm", "--noprogressbar", "-U", source
end
def install_from_repo
resource_name = @resource[:name]
# Refuse to install if not allowing virtual packages and the resource is a group
fail(_("Refusing to install package group %{resource_name}, because allow_virtual is false.") % { resource_name: resource_name }) if self.class.group?(resource_name) && !@resource.allow_virtual?
cmd = %w{--noconfirm --needed}
cmd += install_options if @resource[:install_options]
if install_with_aura?
cmd << "-Aq" << resource_name
aura(*cmd)
else
cmd << "--noprogressbar"
cmd << "-Sy" << resource_name
pacman(*cmd)
end
end
end
|