]>
Commit | Line | Data |
---|---|---|
d8f933bd IB |
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 |