zypper 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG ADDED
@@ -0,0 +1,16 @@
1
+ 0.0.9 (2012-06-26)
2
+ ------------------
3
+ * Split into smaller one-task libraries
4
+ * Added plural aliases for for accessing subclasses (patch -> patches,
5
+ package -> packages, ...)
6
+ * Generic package search
7
+ * Extended Patch with applicable?() and install() methods
8
+ * Added testsuites
9
+ * Packaging into gem
10
+
11
+ 0.0.1 (2012-06-20)
12
+ ------------------
13
+ * Initial implementation using POpen4
14
+ * Added Services and Repositories handling
15
+ * Added Packages and Packages handling
16
+ * Added documentation
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+ =====================
3
+
4
+ Copyright (c) 2012 Lukas Ocilka
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10
+ of the Software, and to permit persons to whom the Software is furnished to do
11
+ so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,421 @@
1
+ # Zypper Library #
2
+
3
+ This library provides Ruby access to libzypp using the
4
+ `zypper` command.
5
+
6
+ ## License ##
7
+
8
+ Distributed under the MIT license, see LICENSE file.
9
+
10
+ ## Features ###
11
+
12
+ * Easy-to-use API
13
+ * Running zypper in chroot or running your system zypper over a different root directory
14
+ * Handling packages, repositories and patches
15
+ * Easy to extend
16
+
17
+ ## Usage ##
18
+
19
+ Require the library
20
+
21
+ ```ruby
22
+ require "zypper"
23
+ ```
24
+
25
+ Initialize new access to the library methods
26
+
27
+ ```ruby
28
+ zypper = Zypper.new()
29
+ ```
30
+
31
+ Add a new repository
32
+
33
+ ```ruby
34
+ zypper.repository.add(:url => 'http://example.org/new/repo', :alias => 'Repo_at_Example_Org')
35
+ ```
36
+
37
+ ## Public Methods ##
38
+
39
+ ### Global ###
40
+
41
+ #### Constructor ###
42
+
43
+ ```ruby
44
+ zypper = Zypper.new(parameters)
45
+ # or
46
+ config = Zypper::Config.new(parameters)
47
+ zypper = Zypper.new(config)
48
+ ```
49
+
50
+ All parameters are optional, using their default value if not set.
51
+
52
+ Possible parameters in hash:
53
+ * :root => '/changed-root' - defaults to '/'
54
+ * :chroot_method => 'local' (calls local zypper with changed root) or 'chroot' (calls zypper in chroot)
55
+ * :refresh_repo => true or false - default for all newly added repositories
56
+ * :auto_agree_with_licenses => true or false - default for installing packages, applying patches...
57
+
58
+ Example:
59
+ ```ruby
60
+ zypper = Zypper.new(:chroot => '/some-chroot-dir', :chroot_method => 'chroot')
61
+ ```
62
+
63
+ #### Zypper Output ####
64
+
65
+ These methods work after calling all API functions:
66
+
67
+ * last_message - unparsed STDOUT of the last zypper call
68
+ * last_error_message - unparsed STDERR of the last zypper call
69
+ * last_exit_status - exit code (integer) of the last zypper call
70
+
71
+ Example:
72
+ ```ruby
73
+ zypper.last_message
74
+ ```
75
+
76
+ #### Zypper Version ####
77
+
78
+ ```ruby
79
+ version
80
+
81
+ # returns
82
+ {:major=>1, :minor=>3, :revision=>7}
83
+ ```
84
+
85
+ #### Caches Cleanup ####
86
+
87
+ ```ruby
88
+ clean_caches
89
+
90
+ # returns
91
+ true or false
92
+ ```
93
+
94
+ #### Importing All used GPG Keys ####
95
+
96
+ ```ruby
97
+ auto_import_keys
98
+
99
+ # returns
100
+ true or false
101
+ ```
102
+
103
+ ### Repositories ###
104
+
105
+ You can access the repositories class either with
106
+ ```ruby
107
+ zypper = Zypper.new
108
+ zypper.repository
109
+ # or
110
+ zypper.repositories
111
+ ```
112
+ or
113
+ ```ruby
114
+ Zypper::Repository.new
115
+ ```
116
+
117
+ #### Listing repositories ####
118
+
119
+ ```ruby
120
+ zypper.repositories.all
121
+
122
+ # returns
123
+ [
124
+ { "enabled"=>"1", "autorefresh"=>"1", "name"=>"SLES11-SP1-x68_64", "url"=>["http://repo/URI"],
125
+ "type"=>"rpm-md", "alias"=>"repository_alias", "gpgcheck"=>"1" },
126
+ { ... },
127
+ ...
128
+ ]
129
+ ```
130
+
131
+ #### Adding a Repository ####
132
+
133
+ ```ruby
134
+ zypper.repository.add(:url => 'http://repository/URI', :alias => 'repository_alias')
135
+
136
+ # returns
137
+ true or false
138
+ ```
139
+
140
+ Example:
141
+ ```ruby
142
+ Zypper.new.repository.add(:url => 'http://repository/URI', :alias => 'repository_alias')
143
+ # or
144
+ Zypper::Repository.new.add(:url => 'http://repository/URI', :alias => 'repository_alias')
145
+ ```
146
+
147
+ #### Removing a Repository ####
148
+
149
+ ```ruby
150
+ zypper.repository.remove(:alias => 'repository_alias')
151
+
152
+ # returns
153
+ true or false
154
+ ```
155
+
156
+ #### Refreshing Repositories ####
157
+
158
+ ```ruby
159
+ zypper.repository.refresh(parameters)
160
+
161
+ # returns
162
+ true or false
163
+ ```
164
+
165
+ Possible optional parameters:
166
+ * :force - forces a complete refresh
167
+ * :force_build - forces rebuild of the database
168
+
169
+ ### Services ###
170
+
171
+ You can access the services class either with
172
+ ```ruby
173
+ zypper = Zypper.new
174
+ zypper.service
175
+ # or
176
+ zypper.services
177
+ ```
178
+ or
179
+ ```ruby
180
+ Zypper::Service.new
181
+ ```
182
+
183
+ #### Listing Services ####
184
+
185
+ ```ruby
186
+ zypper.services.all
187
+ ```
188
+
189
+ Example:
190
+ ```ruby
191
+ Zypper.new.services.all
192
+ # or
193
+ Zypper::Services.new.all
194
+ ```
195
+
196
+ #### Refreshing Services ####
197
+
198
+ ```ruby
199
+ zypper.services.refresh
200
+
201
+ # returns
202
+ true or false
203
+ ```
204
+
205
+ ### Packages ###
206
+
207
+ #### Installing Packages ####
208
+
209
+ You can access the packages class either with
210
+ ```ruby
211
+ zypper = Zypper.new
212
+ zypper.package
213
+ # or
214
+ zypper.packages
215
+ ```
216
+ or
217
+ ```ruby
218
+ Zypper::Package.new
219
+ ```
220
+
221
+ ```ruby
222
+ zypper.packages.install(:packages => ['package', 'package', ...])
223
+
224
+ # returns
225
+ true or false
226
+ ```
227
+
228
+ `package` string can consist of NAME[.ARCH][OP<VERSION>], where OP is one
229
+ of <, <=, =, >=, >, for example:
230
+
231
+ ```ruby
232
+ zypper.package.install :packages => ['less.x86_64=424b-10.22']
233
+ ```
234
+
235
+ #### Removing Packages ####
236
+
237
+ ```ruby
238
+ zypper.packages.remove(:packages => ['package', 'package', ...])
239
+
240
+ # returns
241
+ true or false
242
+ ```
243
+
244
+ `package` string can consist of NAME[.ARCH][OP<VERSION>], where OP is one
245
+ of <, <=, =, >=, >, for example:
246
+
247
+ ```ruby
248
+ zypper.package.remove :packages => ['less.x86_64=424b-10.22']
249
+ ```
250
+
251
+ #### Installed Packages ####
252
+
253
+ Lists all installed packages.
254
+
255
+ ```ruby
256
+ zypper.packages.installed
257
+
258
+ # returns
259
+ [
260
+ {
261
+ :type=>"package", :status=>:installed, :summary=>"Package, Patch, Pattern, and Product Management",
262
+ :name=>"libzypp"
263
+ },
264
+ { ... },
265
+ ...
266
+ ]
267
+ ```
268
+
269
+ #### Available Packages ####
270
+
271
+ ```ruby
272
+ zypper.packages.available
273
+
274
+ # returns
275
+ [
276
+ {
277
+ :type=>"package", :status=>:available, :summary=>"Helper that makes writing ...",
278
+ :name=>"zypp-plugin-python"
279
+ },
280
+ { ... },
281
+ ...
282
+ ]
283
+ ```
284
+
285
+ #### Packages Search ####
286
+
287
+ ```ruby
288
+ zypper.packages.find
289
+
290
+ # returns
291
+ [
292
+ ... list of packages ...
293
+ ]
294
+ ```
295
+
296
+ Example
297
+ ```ruby
298
+ # All packages with name 'kernel-default'
299
+ zypper.packages.find(:name => 'kernel-default')
300
+
301
+ # All available packages matching zypp*
302
+ zypper.packages.find(:name => 'zypp*', :status => :available)
303
+
304
+ # All installed packages
305
+ zypper.packages.find(:status => :installed)
306
+ ```
307
+
308
+ #### Package Info ####
309
+
310
+ ```ruby
311
+ zypper.package.info(:package => 'package')
312
+
313
+ # Returns, e.g.
314
+ {
315
+ :status=>"not installed", :version=>"424b-10.22",
316
+ :summary=>"Text File Browser and Pager Similar to more", :arch=>"x86_64",
317
+ :repository=>"SLES11-SP1-x68_64", :size=>"266.0 KiB",
318
+ :vendor=>"SUSE LINUX Products GmbH, Nuernberg, Germany",
319
+ :name=>"less", :installed=>"No", :level=>"Level 3"
320
+ }
321
+ ```
322
+
323
+ #### Package Installed? ####
324
+
325
+ ```ruby
326
+ zypper.package.installed?(:package => 'package')
327
+
328
+ # returns
329
+ true or false
330
+ ```
331
+
332
+ ### Patches ###
333
+
334
+ You can access the patches class either with
335
+
336
+ ```ruby
337
+ zypper = Zypper.new
338
+ zypper.patch
339
+ # or
340
+ zypper.patches
341
+ ```
342
+ or
343
+ ```ruby
344
+ Zypper::patch.new
345
+ ```
346
+
347
+ #### Listing Patches ####
348
+
349
+ All known patches
350
+
351
+ ```ruby
352
+ zypper.patches.all
353
+ ```
354
+
355
+ #### Searching for Patches ####
356
+
357
+ ```ruby
358
+ zypper.patches.find(filter_parameters)
359
+
360
+ # returns
361
+ [
362
+ { :status=>'Needed', :category=>'Recommended', :name=>'patch-name',
363
+ :version=>'patch-version', :catalog=>"repository-name"
364
+ },
365
+ { ... },
366
+ ...
367
+ ]
368
+ ```
369
+ All parameters are optional and can be combined, using their default value if not set.
370
+
371
+ Possible parameters in hash:
372
+
373
+ * :status => 'Status' (see Zypper::Patch::Status class constants)
374
+ * :category => 'Category' (See Zypper::Patch::Category class constants)
375
+ * :name => 'Exact-Name'
376
+ * :version => 'Exact-Version'
377
+ * :catalog => 'Alias-of-the-repo'
378
+
379
+ Example:
380
+ ```ruby
381
+ zypper.patches.all(
382
+ :status => Zypper::Patch::Status::INSTALLED,
383
+ :category => Zypper::Patch::Category::RECOMMENDED
384
+ )
385
+ ```
386
+
387
+ #### Applicable Patches ####
388
+
389
+ Lists all applicable patches. All filter parameters are optional and can
390
+ be used the same as for the `find()` method.
391
+
392
+ ```ruby
393
+ zypper.patches.applicable(filter_parameters)
394
+ ```
395
+
396
+ #### Any Applicable Patches? ####
397
+
398
+ Returns whether there are any applicable patches present.
399
+ All filter parameters are optional and can be used the same as for the
400
+ `find()` method.
401
+
402
+ ```ruby
403
+ zypper.patches.applicable?(filter_parameters)
404
+ ```
405
+
406
+ #### Installed Patches
407
+
408
+ Lists all installed patches. All filter parameters are optional and can
409
+ be used the same as for the `find()` method.
410
+
411
+ ```ruby
412
+ zypper.patches.installed(filter_parameters)
413
+ ```
414
+
415
+ #### Install Patches ####
416
+
417
+ Installs all applicable patches.
418
+
419
+ ```ruby
420
+ zypper.patches.install
421
+ ```
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
data/lib/zypper.rb ADDED
@@ -0,0 +1,55 @@
1
+ require 'zypper/utils'
2
+
3
+ require 'zypper/repository'
4
+ require 'zypper/service'
5
+ require 'zypper/package'
6
+ require 'zypper/patch'
7
+
8
+ class Zypper
9
+ include ZypperUtils
10
+
11
+ attr_reader :repository, :service, :package, :patch
12
+
13
+ alias :repositories :repository
14
+ alias :services :service
15
+ alias :packages :package
16
+ alias :patches :patch
17
+
18
+ def initialize(params = {})
19
+ super(params)
20
+
21
+ self.repository = Zypper::Repository.new config
22
+ self.service = Zypper::Service.new config
23
+ self.package = Zypper::Package.new config
24
+ self.patch = Zypper::Patch.new config
25
+ end
26
+
27
+ # Returns the current zypper version
28
+ def version(options = {})
29
+ if (run(build_command('version', options = {})))
30
+ version_number(last_message, options)
31
+ end
32
+ end
33
+
34
+ # Cleans the libzypp cache
35
+ def clean_caches(options = {})
36
+ run build_command('clean', options)
37
+ end
38
+
39
+ # Auto-imports all GPG keys from repositories
40
+ def auto_import_keys(options = {})
41
+ previous_auto_import_gpg = config.auto_import_gpg
42
+ ret = repositories.refresh
43
+ config.auto_import_gpg = previous_auto_import_gpg
44
+ ret
45
+ end
46
+
47
+ private
48
+
49
+ attr_writer :repository, :service, :package, :patch
50
+
51
+ def version_number(version_string, options)
52
+ version = version_string.gsub(/[^0-9\.]/, '').split('.')
53
+ {:major => version[0].to_i, :minor => version[1].to_i, :revision => version[2].to_i}
54
+ end
55
+ end
@@ -0,0 +1,91 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+
4
+ class Zypper
5
+ class Config
6
+
7
+ DEFAULT_ROOT = '/'
8
+ DEFAULT_IMPORT_GPG = true
9
+ DEFAULT_REFRESH_REPO = true
10
+ DEFAULT_AUTO_AGREE_WITH_LICENSES = true
11
+ DEFAULT_CHROOT_METHOD = 'local'
12
+
13
+ CHROOT_METHOD_LOCAL = 'local'
14
+ CHROOT_METHOD_CHROOT = 'chroot'
15
+ KNOWN_CHROOT_METHODS = [CHROOT_METHOD_LOCAL, CHROOT_METHOD_CHROOT]
16
+
17
+ # Constructor
18
+ #
19
+ # Possible parameters
20
+ # (string) :root
21
+ # Defines the changed root environment, default '/'
22
+ # (boolean) :auto_import_gpg
23
+ # Automatically trust (and import) new GPG keys, default true
24
+ # (boolean) :refresh_repo
25
+ # Adds new repositories with autorefresh flag, default true
26
+ # (string) :chroot_method
27
+ # Defines which zypper is used; 'local' uses the local zypper with
28
+ # changed root directory specified as --root parameter whereas
29
+ # 'chroot' uses chroot binary and calls zypper directly in the
30
+ # :root directory. This can be ignored if changed :root is not
31
+ # defined
32
+ # (boolean :auto_agree_with_licenses
33
+ # automatically accept all licenses, otherwise such packages
34
+ # cannot be installed
35
+ def initialize(params = {})
36
+ self.root = (params[:root] || DEFAULT_ROOT)
37
+ self.chroot_method = (params[:chroot_method] || DEFAULT_CHROOT_METHOD)
38
+
39
+ @auto_import_gpg = (params[:auto_import_gpg] || DEFAULT_IMPORT_GPG)
40
+ @refresh_repo = (params[:refresh_repo] || DEFAULT_REFRESH_REPO)
41
+ @auto_agree_with_licenses = (params[:auto_agree_with_licenses] || DEFAULT_AUTO_AGREE_WITH_LICENSES)
42
+ end
43
+
44
+ attr_accessor :auto_import_gpg
45
+
46
+ attr_reader :root, :chroot_method
47
+
48
+ # Changes the current root directory, the directory must exist
49
+ def root=(new_root)
50
+ if ! File.exists? new_root
51
+ raise "Directory #{new_root} does not exist"
52
+ elsif ! File.directory? new_root
53
+ raise "#{new_root} is not a directory"
54
+ end
55
+
56
+ @root = new_root
57
+ end
58
+
59
+ # Changes the current chroot method, see constructor for possible values
60
+ def chroot_method=(new_chroot_method)
61
+ unless KNOWN_CHROOT_METHODS.include? new_chroot_method
62
+ raise "Unknown chroot method #{new_chroot_method}, possible are #{KNOWN_CHROOT_METHODS.join(', ')}"
63
+ end
64
+
65
+ @chroot_method = new_chroot_method
66
+ end
67
+
68
+ # Using chroot command
69
+ def chrooted?
70
+ chroot_method == CHROOT_METHOD_CHROOT
71
+ end
72
+
73
+ # Using zypper --root of the root is actually different
74
+ def changed_root?
75
+ root != DEFAULT_ROOT && chroot_method == CHROOT_METHOD_LOCAL
76
+ end
77
+
78
+ def auto_import_gpg?
79
+ @auto_import_gpg
80
+ end
81
+
82
+ def auto_agree_with_licenses?
83
+ @auto_agree_with_licenses
84
+ end
85
+
86
+ def refresh_repo?
87
+ @refresh_repo
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,115 @@
1
+ require 'zypper/utils'
2
+
3
+ class Zypper
4
+ class Package
5
+ include ZypperUtils
6
+
7
+ class Status
8
+ INSTALLED = :installed
9
+ AVAILABLE = :uninstalled
10
+ end
11
+
12
+ PACKAGE_STATUSES = {
13
+ '' => Status::AVAILABLE,
14
+ 'i' => Status::INSTALLED,
15
+ }
16
+
17
+ # Installs packages given as parmeter
18
+ # (array) :packages
19
+ def install(options = {})
20
+ run build_command('install', options)
21
+ end
22
+
23
+ # Removes packages given as parmeter
24
+ # (array) :packages
25
+ def remove(options = {})
26
+ run build_command('remove', options)
27
+ end
28
+
29
+ # Returns hash of information on a package given as parameter
30
+ # (string) :package
31
+ def info(options = {})
32
+ if (run(build_command('info', options)))
33
+ convert_info(last_message)
34
+ end
35
+ end
36
+
37
+ # returns whether a package given as parameter is installed
38
+ # (string) :package
39
+ def installed?(options = {})
40
+ info(options).fetch(Status::INSTALLED, 'No') == 'Yes'
41
+ end
42
+
43
+ # Returns packages found using given parameters
44
+ #
45
+ # @param (Hash) of options
46
+ # (string) :name - exact name of a package
47
+ # (symbol) :status - See Zypper::Package::Status class constants
48
+ def find(options = {})
49
+ additional_options = {:cmd_options => ['--type package'], :quiet => true}
50
+
51
+ if (run (build_command('search', options.merge(additional_options))))
52
+ convert_packages(last_message)
53
+ end
54
+ end
55
+
56
+ # Returns all installed packages
57
+ def installed(options = {})
58
+ find(options.merge(:status => Status::INSTALLED))
59
+ end
60
+
61
+ # Returns all available packages (that are not installed yet)
62
+ def available(options = {})
63
+ find(options.merge(:status => Status::AVAILABLE))
64
+ end
65
+
66
+ private
67
+
68
+ def status(status)
69
+ return PACKAGE_STATUSES[status] if PACKAGE_STATUSES[status]
70
+
71
+ raise "Unknown package status '#{status}'"
72
+ end
73
+
74
+ # SLE11 zypper doesn't support XML output for packages
75
+ # FIXME: merge with 'convert_patches'
76
+ def convert_packages(packages)
77
+ out = []
78
+ table_index = 0
79
+ package = {}
80
+
81
+ packages.split("\n").each {|line|
82
+ table_index = table_index + 1
83
+ # Skip the first two - table header
84
+ next if table_index < 3
85
+
86
+ line.gsub!(/ +\| +/, '|')
87
+ line.gsub!(/^ +/, '')
88
+ line.gsub!(/ +$/, '')
89
+ package = line.split '|'
90
+
91
+ out.push(
92
+ :status => status(package[0]),
93
+ :name => package[1],
94
+ :summary => package[2],
95
+ :type => package[3]
96
+ )
97
+ }
98
+
99
+ out
100
+ end
101
+
102
+ def convert_info(info)
103
+ out = {}
104
+
105
+ info.split("\n").each do |line|
106
+ if /([[:alnum:]]+): (.+)/.match(line)
107
+ out[$1.downcase.to_sym] = $2
108
+ end
109
+ end
110
+
111
+ out
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,113 @@
1
+ require 'zypper/utils'
2
+
3
+ class Zypper
4
+ class Patch
5
+ class Status
6
+ NEEDED = 'Needed'
7
+ INSTALLED = 'Installed'
8
+ NOT_APPLICABLE = 'Not Applicable'
9
+ end
10
+
11
+ class Category
12
+ SECURITY = 'security'
13
+ RECOMMENDED = 'recommended'
14
+ FEATURE = 'feature'
15
+ OPTIONAL = 'optional'
16
+ end
17
+
18
+ include ZypperUtils
19
+
20
+ FILTER_OPTIONS = [:repository, :name, :version, :category, :status]
21
+
22
+ # Lists all patches
23
+ #
24
+ # @param Hash with optional key :where (Hash)
25
+ # that can consist of one or more parameters from
26
+ # :repository, :name, :version, :category, and :status.
27
+ # Logical AND is always applied for all the options present
28
+ #
29
+ # @example
30
+ # all(:status => 'Installed')
31
+ def find(options = {})
32
+ additional_options = {:quiet => true}
33
+
34
+ if (run(build_command('patches', options.merge(additional_options))))
35
+ apply_filters(convert_patches(last_message), options)
36
+ end
37
+ end
38
+
39
+ # Lists all known patches
40
+ def all(options = {})
41
+ find
42
+ end
43
+
44
+ # All applicable patches
45
+ def applicable(options = {})
46
+ find(options.merge(:status => Status::NEEDED))
47
+ end
48
+
49
+ # Are there any applicable patches present?
50
+ def applicable?(options = {})
51
+ applicable(options).size > 0
52
+ end
53
+
54
+ # Installs all applicable patches
55
+ def install(options = {})
56
+
57
+ run(build_command('patch', options))
58
+ end
59
+
60
+ # All installed patches
61
+ def installed(options = {})
62
+ find(options.merge(:status => Status::INSTALLED))
63
+ end
64
+
65
+ private
66
+
67
+ # Current libzypp doesn't support XML output for patches
68
+ def convert_patches(patches)
69
+ out = []
70
+ table_index = 0
71
+ patch = {}
72
+
73
+ patches.split("\n").each {|line|
74
+ table_index = table_index + 1
75
+ # Skip the first two - table header
76
+ next if table_index < 3
77
+
78
+ line.gsub!(/ +\| +/, '|')
79
+ line.gsub!(/^ +/, '')
80
+ line.gsub!(/ +$/, '')
81
+ patch = line.split '|'
82
+
83
+ out.push(
84
+ :repository => patch[0],
85
+ :name => patch[1],
86
+ :version => patch[2],
87
+ :category => patch[3],
88
+ :status => patch[4]
89
+ )
90
+ }
91
+
92
+ out
93
+ end
94
+
95
+ # Filters patches according to given parameters
96
+ #
97
+ # @param (Array) patches
98
+ # @param (Hash) filters criteria, possible keys are :repository, :name, :version, :category and :status
99
+ #
100
+ # @example
101
+ # apply_patch_filters(patches, { :status => 'Needed' })
102
+ # apply_patch_filters(patches, { :version' => '1887', :repository => 'SLES11-SP1-Update' })
103
+ def apply_filters(patches = [], filters = {})
104
+ filters.each {|filter_key, filter_value|
105
+ raise "Unknown filter parameter '#{filter_key}'" unless FILTER_OPTIONS.include? filter_key
106
+
107
+ patches = patches.select{|patch| patch[filter_key] == filter_value}
108
+ }
109
+ patches
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,35 @@
1
+ require 'zypper/utils'
2
+
3
+ class Zypper
4
+ class Repository
5
+ include ZypperUtils
6
+
7
+ # Refreshes repositories
8
+ # @param (optional) options
9
+ # :force - to force the refresh
10
+ # :force_rebuild - forces rebuilding the libzypp database
11
+ def refresh(options = {})
12
+ run build_command('refresh', options)
13
+ end
14
+
15
+ # Lists all known repositories
16
+ def all(options = {})
17
+ out = xml_run build_command('repos', options.merge(:get => XML_COMMANDS_GET))
18
+ out.fetch('repo-list', []).fetch(0, {}).fetch('repo', [])
19
+ end
20
+
21
+ # Adds a new repository defined by options
22
+ # (string) :url URL/URL
23
+ # (string) :alias
24
+ def add(options = {})
25
+ run build_command('addrepo', options)
26
+ end
27
+
28
+ # Removes a repository defined by options
29
+ # (string) :alias
30
+ def remove(options = {})
31
+ run build_command('removerepo', options)
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ require 'zypper/utils'
2
+
3
+ class Zypper
4
+ class Service
5
+ include ZypperUtils
6
+
7
+ # Refreshes services
8
+ def refresh(options = {})
9
+ run build_command('refresh-services', options)
10
+ end
11
+
12
+ # Lists all known services
13
+ def all(options = {})
14
+ out = xml_run build_command('services', options.merge(:get => XML_COMMANDS_GET))
15
+ out.fetch('service-list', []).fetch(0, {}).fetch('service', [])
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,197 @@
1
+ module ZypperUtils
2
+ require 'rubygems'
3
+ require 'shellwords'
4
+ require 'popen4'
5
+ require 'xmlsimple'
6
+
7
+ require 'zypper/config'
8
+
9
+ XML_COMMANDS_GET = 'xml'
10
+
11
+ # Only getters are public
12
+ attr_reader :last_message, :last_error_message, :last_exit_status, :config
13
+
14
+ # Constructor
15
+ #
16
+ # @param either instance of Zypper::Config class
17
+ # or hash of options for new Zypper::Config class
18
+ #
19
+ # See Zypper::Config new() for more info
20
+ def initialize(params = {})
21
+ # Config is the given parameter
22
+ if (params.instance_of?(Zypper::Config))
23
+ self.config = params
24
+ # Called directly
25
+ elsif (params.is_a?(Hash))
26
+ self.config = Zypper::Config.new params
27
+ # Unknown call method
28
+ else
29
+ raise "Parameters #{params.inspect} is neither Zypper::Config nor Hash with options"
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ # Setters are private
36
+ attr_writer :last_message, :last_error_message, :last_exit_status, :config
37
+
38
+ def check_mandatory_options_set(zypper_action, options, mandatory)
39
+ mandatory.each {|option|
40
+ raise "Missing '#{option}' parameter in '#{zypper_action}' action" if options[option].nil?
41
+ }
42
+ end
43
+
44
+ def check_mandatory_options(zypper_action, options)
45
+ case zypper_action
46
+ when 'addrepo'
47
+ check_mandatory_options_set(zypper_action, options, [:url, :alias])
48
+ # FIXME: check that :url or :alias do not contain any spaces (or special characters)
49
+ when 'removerepo'
50
+ check_mandatory_options_set(zypper_action, options, [:alias])
51
+ # FIXME: check that :url or :alias do not contain any spaces (or special characters)
52
+ when 'install'
53
+ # FIXME: check that :packages do not contain any spaces (or special characters)
54
+ check_mandatory_options_set(zypper_action, options, [:packages])
55
+ when 'remove'
56
+ # FIXME: check that :packages do not contain any spaces (or special characters)
57
+ check_mandatory_options_set(zypper_action, options, [:packages])
58
+ when 'info'
59
+ # FIXME: check that :package does not contain any spaces (or special characters)
60
+ check_mandatory_options_set(zypper_action, options, [:package])
61
+ # No checks:
62
+ # * 'search' used also just with command-line parameters but no particular
63
+ # object to search for
64
+ #
65
+ end
66
+ end
67
+
68
+ def chrooted
69
+ config.chrooted? ? 'chroot ' + Shellwords::escape(config.root) + ' ' : ''
70
+ end
71
+
72
+ # Returns the full zypper command including chroot, zypper command, options, etc.
73
+ def build_command(zypper_action, options = {})
74
+ check_mandatory_options(zypper_action, options)
75
+
76
+ chrooted + ' zypper ' + global_options(options) + ' ' +
77
+ zypper_command(zypper_action) + ' ' + zypper_command_options(zypper_action, options)
78
+ end
79
+
80
+ # Returns a zypper command (shell) defined by an action
81
+ def zypper_command zypper_action
82
+ case zypper_action
83
+ # version is a global option but not a command
84
+ when 'version'
85
+ ''
86
+ else
87
+ zypper_action
88
+ end
89
+ end
90
+
91
+ def escape_items(items = [])
92
+ items.collect{|package| Shellwords::escape(package)}.join(' ')
93
+ end
94
+
95
+ def escape(item = '')
96
+ Shellwords::escape(item)
97
+ end
98
+
99
+ # Returns string of command options depending on a given zypper command
100
+ # combined with provided options
101
+ def zypper_command_options(zypper_action, options = {})
102
+ ret_options = []
103
+
104
+ # Additional command-line options for a command
105
+ if options[:cmd_options]
106
+ ret_options = options[:cmd_options]
107
+ end
108
+
109
+ case zypper_action
110
+ when 'refresh'
111
+ ret_options = [
112
+ options[:force] ? '--force' : '',
113
+ options[:force_build] ? '--force-build' : '',
114
+ ]
115
+ when 'addrepo'
116
+ ret_options = [
117
+ config.refresh_repo? ? '--refresh':'',
118
+ options[:url],
119
+ options[:alias],
120
+ ]
121
+ when 'removerepo'
122
+ ret_options = [
123
+ options[:alias],
124
+ ]
125
+ when 'install'
126
+ ret_options = [
127
+ config.auto_agree_with_licenses? ? '--auto-agree-with-licenses' : '',
128
+ escape_items(options[:packages]),
129
+ ]
130
+ when 'remove'
131
+ ret_options = [
132
+ escape_items(options[:packages]),
133
+ ]
134
+ when 'version'
135
+ ret_options = [
136
+ '--version',
137
+ ]
138
+ when 'info'
139
+ ret_options = [
140
+ escape(options[:package]),
141
+ ]
142
+ when 'search'
143
+ ret_options = [
144
+ options[:status] == Zypper::Package::Status::INSTALLED ? '--installed-only' : '',
145
+ options[:status] == Zypper::Package::Status::AVAILABLE ? '--uninstalled-only' : '',
146
+
147
+ options[:name] ? '--match-exact ' + escape(options[:name]) : '',
148
+ ]
149
+ end
150
+
151
+ ret_options.join(' ')
152
+ end
153
+
154
+ # Returns string with global zypper options
155
+ def global_options(options = {})
156
+ [
157
+ (options[:quiet] ? '--quiet' : ''),
158
+ (config.changed_root? ? '--root=' + Shellwords::escape(config.root) : ''),
159
+ (options[:get] == XML_COMMANDS_GET ? '--xmlout' : ''),
160
+ '--non-interactive',
161
+ (config.auto_import_gpg? ? '--gpg-auto-import-keys' : ''),
162
+ ].join(' ')
163
+ end
164
+
165
+ def xml_run(command)
166
+ xml = run(command, {:get => XML_COMMANDS_GET})
167
+ out = XmlSimple.xml_in(xml)
168
+
169
+ if !out["message"].nil?
170
+ errors = out["message"].select{|hash| hash["type"] == "error"}
171
+ self.last_error = errors.collect{|hash| hash["content"]}.join("\n")
172
+ end
173
+
174
+ out
175
+ end
176
+
177
+ # Runs a command given as argument and returns the full output
178
+ # Exit status can be acquired using last_exit_status call
179
+ def run(command, params = {})
180
+ # FIXME: it's here just for debugging
181
+ puts "DEBUG: " + command
182
+
183
+ cmd_ret = POpen4::popen4(command) do |stdout, stderr, stdin, pid|
184
+ self.last_message = stdout.read.strip
185
+ self.last_error_message = stderr.read.strip
186
+ end
187
+
188
+ last_exit_status = cmd_ret.exitstatus
189
+
190
+ if params[:get] == XML_COMMANDS_GET
191
+ last_message
192
+ else
193
+ last_exit_status == 0
194
+ end
195
+ end
196
+
197
+ end
@@ -0,0 +1,4 @@
1
+ module Zypper
2
+ # Zypper version (uses [semantic versioning](http://semver.org/)).
3
+ VERSION = File.read(File.dirname(__FILE__) + "/../../VERSION").strip
4
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zypper
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
+ platform: ruby
12
+ authors:
13
+ - Lukas Ocilka
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-06-26 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: popen4
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: xml-simple
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: mocha
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :development
61
+ version_requirements: *id003
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ prerelease: false
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ type: :development
75
+ version_requirements: *id004
76
+ description: |-
77
+ Library for accessing zypper functions such as searching and
78
+ installing packages, adding and removing repositories and services. Supports
79
+ calling zypper in changed root (both with local zypper and zypper in chroot).
80
+ email: lukas.ocilka@gmail.com
81
+ executables: []
82
+
83
+ extensions: []
84
+
85
+ extra_rdoc_files: []
86
+
87
+ files:
88
+ - lib/zypper.rb
89
+ - lib/zypper/config.rb
90
+ - lib/zypper/repository.rb
91
+ - lib/zypper/utils.rb
92
+ - lib/zypper/package.rb
93
+ - lib/zypper/patch.rb
94
+ - lib/zypper/service.rb
95
+ - lib/zypper/version.rb
96
+ - LICENSE
97
+ - CHANGELOG
98
+ - README.markdown
99
+ - VERSION
100
+ homepage: https://github.com/kobliha/zypper
101
+ licenses:
102
+ - MIT
103
+ post_install_message:
104
+ rdoc_options: []
105
+
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ hash: 3
123
+ segments:
124
+ - 0
125
+ version: "0"
126
+ requirements: []
127
+
128
+ rubyforge_project:
129
+ rubygems_version: 1.8.15
130
+ signing_key:
131
+ specification_version: 3
132
+ summary: Library for accessing zypper
133
+ test_files: []
134
+
135
+ has_rdoc: