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