zypper 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: