enhancerepo 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/CHANGELOG.rdoc +18 -0
  2. data/Manifest.txt +85 -0
  3. data/README +10 -0
  4. data/README.rdoc +15 -0
  5. data/Rakefile +48 -0
  6. data/TODO.rdoc +4 -0
  7. data/bin/enhancerepo +4 -0
  8. data/lib/enhance_repo.rb +36 -0
  9. data/lib/enhance_repo/cli.rb +150 -0
  10. data/lib/enhance_repo/config_opts.rb +98 -0
  11. data/lib/enhance_repo/logger.rb +67 -0
  12. data/lib/enhance_repo/other.rb +80 -0
  13. data/lib/enhance_repo/package_id.rb +81 -0
  14. data/lib/enhance_repo/product_id.rb +30 -0
  15. data/lib/enhance_repo/rpm_md.rb +27 -0
  16. data/lib/enhance_repo/rpm_md/data.rb +55 -0
  17. data/lib/enhance_repo/rpm_md/delta_info.rb +205 -0
  18. data/lib/enhance_repo/rpm_md/extra_primary_data.rb +45 -0
  19. data/lib/enhance_repo/rpm_md/file_lists.rb +81 -0
  20. data/lib/enhance_repo/rpm_md/index.rb +144 -0
  21. data/lib/enhance_repo/rpm_md/other.rb +69 -0
  22. data/lib/enhance_repo/rpm_md/primary.rb +123 -0
  23. data/lib/enhance_repo/rpm_md/products.rb +142 -0
  24. data/lib/enhance_repo/rpm_md/repo.rb +202 -0
  25. data/lib/enhance_repo/rpm_md/resource.rb +44 -0
  26. data/lib/enhance_repo/rpm_md/suse_data.rb +191 -0
  27. data/lib/enhance_repo/rpm_md/suse_info.rb +88 -0
  28. data/lib/enhance_repo/rpm_md/update.rb +141 -0
  29. data/lib/enhance_repo/rpm_md/update_info.rb +244 -0
  30. data/lib/enhance_repo/rpm_md/update_smart_fields.rb +127 -0
  31. data/lib/enhance_repo/xml_comparer.rb +117 -0
  32. data/lib/tempdir.rb +62 -0
  33. data/lib/tempdir/tempfile.rb +21 -0
  34. data/test/all_tests.rb +27 -0
  35. data/test/data/packages/a-1.0.spec +19 -0
  36. data/test/data/packages/a-2.0.spec +24 -0
  37. data/test/data/repodata/update-test-11.1/filelists.xml.gz +0 -0
  38. data/test/data/repodata/update-test-11.1/other.xml.gz +0 -0
  39. data/test/data/repodata/update-test-11.1/primary.xml.gz +0 -0
  40. data/test/data/repodata/update-test-11.1/repomd.xml +21 -0
  41. data/test/data/repomd.xml +21 -0
  42. data/test/data/rpms/repo-1/a-1.0-0.x86_64.rpm +0 -0
  43. data/test/data/rpms/repo-1/a-1.0_2.0-0_0.x86_64.delta.rpm +0 -0
  44. data/test/data/rpms/repo-1/a-2.0-0.x86_64.rpm +0 -0
  45. data/test/data/rpms/repo-1/repodata/deltainfo.xml.gz +0 -0
  46. data/test/data/rpms/repo-1/repodata/deltainfo.xml.gz.gz +0 -0
  47. data/test/data/rpms/repo-1/repodata/filelists.xml.gz +0 -0
  48. data/test/data/rpms/repo-1/repodata/other.xml.gz +0 -0
  49. data/test/data/rpms/repo-1/repodata/primary.xml.gz +0 -0
  50. data/test/data/rpms/repo-1/repodata/repomd.xml +45 -0
  51. data/test/data/rpms/repo-1/repodata/susedata.xml.gz +0 -0
  52. data/test/data/rpms/repo-1/repodata/updateinfo.xml.gz +0 -0
  53. data/test/data/rpms/repo-1/repoparts/update-a-1.xml +22 -0
  54. data/test/data/rpms/repo-with-product/a-1.0-0.x86_64.rpm +0 -0
  55. data/test/data/rpms/repo-with-product/openSUSE-release-11.2-1.6.i586.rpm +0 -0
  56. data/test/data/rpms/repo-with-product/openSUSE-release-dvd-11.2-1.6.i586.rpm +0 -0
  57. data/test/data/rpms/repo-with-product/repodata/filelists.xml.gz +0 -0
  58. data/test/data/rpms/repo-with-product/repodata/other.xml.gz +0 -0
  59. data/test/data/rpms/repo-with-product/repodata/primary.xml.gz +0 -0
  60. data/test/data/rpms/repo-with-product/repodata/products.xml.gz +0 -0
  61. data/test/data/rpms/repo-with-product/repodata/repomd.xml +27 -0
  62. data/test/data/rpms/update-test-11.1/update-test-affects-package-manager.rpm +0 -0
  63. data/test/data/rpms/update-test-11.1/update-test-interactive.rpm +0 -0
  64. data/test/data/rpms/update-test-11.1/update-test-optional.rpm +0 -0
  65. data/test/data/rpms/update-test-11.1/update-test-reboot-needed.rpm +0 -0
  66. data/test/data/rpms/update-test-11.1/update-test-security.rpm +0 -0
  67. data/test/data/rpms/update-test-11.1/update-test-trival.rpm +0 -0
  68. data/test/data/rpms/update-test-factory/update-test-affects-package-manager-0-9999.1.2.noarch.rpm +0 -0
  69. data/test/data/rpms/update-test-factory/update-test-interactive-0-9999.1.2.noarch.rpm +0 -0
  70. data/test/data/rpms/update-test-factory/update-test-optional-0-9999.1.2.noarch.rpm +0 -0
  71. data/test/data/rpms/update-test-factory/update-test-reboot-needed-0-9999.1.2.noarch.rpm +0 -0
  72. data/test/data/rpms/update-test-factory/update-test-relogin-suggested-0-9999.1.2.noarch.rpm +0 -0
  73. data/test/data/rpms/update-test-factory/update-test-security-0-9999.1.2.noarch.rpm +0 -0
  74. data/test/deltainfo_test.rb +50 -0
  75. data/test/extensions_test.rb +31 -0
  76. data/test/index_test.rb +51 -0
  77. data/test/primary_test.rb +51 -0
  78. data/test/products_test.rb +51 -0
  79. data/test/repo_test.rb +72 -0
  80. data/test/repomdindex_test.rb +58 -0
  81. data/test/rpmmd_test.rb +60 -0
  82. data/test/susedata_test.rb +53 -0
  83. data/test/tempdir_test.rb +62 -0
  84. data/test/test_helper.rb +53 -0
  85. data/test/update_test.rb +80 -0
  86. data/test/updateinfo_test.rb +59 -0
  87. metadata +267 -0
@@ -0,0 +1,88 @@
1
+ #--
2
+ #
3
+ # enhancerepo is a rpm-md repository metadata tool.
4
+ # Copyright (C) 2008, 2009 Novell Inc.
5
+ # Author: Duncan Mac-Vicar P. <dmacvicar@suse.de>
6
+ #
7
+ # This program is free software; you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation; either version 2 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
21
+ # also available at http://www.gnu.org/copyleft/gpl.html.
22
+ #
23
+ #++
24
+ #
25
+
26
+ require 'rubygems'
27
+ require 'builder'
28
+
29
+ module EnhanceRepo
30
+ module RpmMd
31
+
32
+ # represents SUSE extensions to repository
33
+ # metadata (not associated with packages)
34
+ #
35
+ # See:
36
+ # http://en.opensuse.org/Standards/Rpm_Metadata#SUSE_repository_info_.28suseinfo.xml.29
37
+ #
38
+ class SuseInfo < Data
39
+
40
+ # expiration time
41
+ # the generated value is
42
+ # still calculated from repomd.xml
43
+ # resources
44
+ attr_accessor :expire
45
+ attr_accessor :products
46
+ attr_accessor :keywords
47
+
48
+ def initialize(dir)
49
+ @dir = dir
50
+ @expire = nil
51
+ @keywords = Set.new
52
+ @products = Set.new
53
+ end
54
+
55
+ def empty?
56
+ @expire.nil? and @products.empty? and @keywords.empty?
57
+ end
58
+
59
+ def write(file)
60
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
61
+ builder.instruct!
62
+ xml = builder.suseinfo do |b|
63
+
64
+ # add expire tag
65
+ b.expire(@expire.to_i.to_s) if not @expire.nil?
66
+
67
+ if not @keywords.empty?
68
+ b.keywords do |b|
69
+ @keywords.each do |k|
70
+ b.k(k)
71
+ end
72
+ end
73
+ end
74
+
75
+ if not @products.empty?
76
+ b.products do |b|
77
+ @products.each do |p|
78
+ b.id(p)
79
+ end
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,141 @@
1
+ #--
2
+ #
3
+ # enhancerepo is a rpm-md repository metadata tool.
4
+ # Copyright (C) 2008, 2009 Novell Inc.
5
+ # Copyright (C) 2009, Jordi Massager Pla <jordi.massagerpla@opensuse.org>
6
+ #
7
+ # Author: Duncan Mac-Vicar P. <dmacvicar@suse.de>
8
+ # Jordi Massager Pla <jordi.massagerpla@opensuse.org>
9
+ #
10
+ # This program is free software; you can redistribute it and/or modify
11
+ # it under the terms of the GNU General Public License as published by
12
+ # the Free Software Foundation; either version 2 of the License, or
13
+ # (at your option) any later version.
14
+ #
15
+ # This program is distributed in the hope that it will be useful,
16
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # GNU General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with this program; if not, write to the Free Software
22
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
23
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
24
+ # also available at http://www.gnu.org/copyleft/gpl.html.
25
+ #
26
+ #++
27
+ #
28
+
29
+ require 'rubygems'
30
+ require 'active_support/core_ext/module/attr_accessor_with_default'
31
+ require 'builder'
32
+ require 'rexml/document'
33
+ require 'yaml'
34
+ require 'prettyprint'
35
+ require 'set'
36
+
37
+ require 'enhance_repo/rpm_md/update_smart_fields'
38
+
39
+ module EnhanceRepo
40
+ module RpmMd
41
+
42
+ #
43
+ # Represents a reference to a external bugreport
44
+ # feature or issue for a software update
45
+ #
46
+ class Reference
47
+ # uri of the reference
48
+ attr_accessor :href
49
+ # its type, for example, bnc (novell's bugzilla)
50
+ attr_accessor :type
51
+ # the id, for example 34561
52
+ # the pair type-id should be globally unique
53
+ attr_accessor :referenceid
54
+ # label to display to the user
55
+ attr_accessor :title
56
+
57
+ def to_s
58
+ "#{type}##{referenceid}"
59
+ end
60
+
61
+ # initialize a reference, per default a novell
62
+ # bugzilla type
63
+ def initialize
64
+ @href = "http://bugzilla.novell.com"
65
+ @referenceid = "none"
66
+ @title = ""
67
+ @type = "bugzilla"
68
+ end
69
+ end
70
+
71
+ # represents one update, which can consist of various packages
72
+ # and references
73
+ class Update
74
+
75
+ # methods to automatically grab data from the
76
+ # update description
77
+ include UpdateSmartFields
78
+
79
+ attr_accessor_with_default :updateid, "unknown"
80
+ attr_accessor_with_default :status, "stable"
81
+ attr_accessor_with_default :from, "#{ENV['USER']}@#{ENV['HOST']}"
82
+ attr_accessor_with_default :type, "optional"
83
+ attr_accessor_with_default :version, 1
84
+ attr_accessor_with_default :release, "unknown"
85
+ attr_accessor_with_default :issued, Time.now.to_i
86
+ attr_accessor_with_default :references, []
87
+ attr_accessor_with_default :description, ""
88
+ attr_accessor_with_default :title, "untitled update"
89
+ attr_accessor_with_default :packages, []
90
+
91
+ def initialize
92
+ end
93
+
94
+ # an update is not empty if it
95
+ # updates something
96
+ def empty?
97
+ packages.empty?
98
+ end
99
+
100
+ def suggested_filename
101
+ "update-#{updateid}-#{version}"
102
+ end
103
+
104
+
105
+ # write a update out
106
+ def write(file)
107
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
108
+ append_to_builder(builder)
109
+ end
110
+
111
+ def append_to_builder(builder)
112
+ builder.update('status' => 'stable', 'from' => from, 'version' => version, 'type' => type) do |b|
113
+ b.title(title)
114
+ b.id(updateid)
115
+ b.issued(issued)
116
+ b.release(release)
117
+ b.description(description)
118
+ # serialize attr_reader :eferences
119
+ b.references do |b|
120
+ references.each do |r|
121
+ b.reference('href' => r.href, 'id' => r.referenceid, 'title' => r.title, 'type' => r.type )
122
+ end
123
+ end
124
+ # done with references
125
+ b.pkglist do |b|
126
+ b.collection do |b|
127
+ packages.each do |pkg|
128
+ b.package('name' => pkg.name, 'arch'=> pkg.arch, 'version'=>pkg.version.v, 'release'=>pkg.version.r) do |b|
129
+ b.filename(File.basename(pkg.path))
130
+ end
131
+ end
132
+ end # </collection>
133
+ end #</pkglist>
134
+ # done with the packagelist
135
+ end
136
+ end
137
+
138
+ end
139
+
140
+ end
141
+ end
@@ -0,0 +1,244 @@
1
+ #--
2
+ #
3
+ # enhancerepo is a rpm-md repository metadata tool.
4
+ # Copyright (C) 2008, 2009 Novell Inc.
5
+ # Copyright (C) 2009, Jordi Massager Pla <jordi.massagerpla@opensuse.org>
6
+ #
7
+ # Author: Duncan Mac-Vicar P. <dmacvicar@suse.de>
8
+ # Jordi Massager Pla <jordi.massagerpla@opensuse.org>
9
+ #
10
+ # This program is free software; you can redistribute it and/or modify
11
+ # it under the terms of the GNU General Public License as published by
12
+ # the Free Software Foundation; either version 2 of the License, or
13
+ # (at your option) any later version.
14
+ #
15
+ # This program is distributed in the hope that it will be useful,
16
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # GNU General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with this program; if not, write to the Free Software
22
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
23
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
24
+ # also available at http://www.gnu.org/copyleft/gpl.html.
25
+ #
26
+ #++
27
+ #
28
+
29
+ require 'rubygems'
30
+ require 'builder'
31
+ require 'rexml/document'
32
+ require 'yaml'
33
+ require 'prettyprint'
34
+ require 'fileutils'
35
+ require 'zlib'
36
+ require 'enhance_repo/rpm_md/update'
37
+
38
+ module EnhanceRepo
39
+ module RpmMd
40
+
41
+ class UpdateInfo < Data
42
+
43
+ def initialize(config)
44
+ @dir = config.dir
45
+ @basedir = config.updatesbasedir
46
+
47
+ # update files
48
+ @updates = Set.new
49
+ end
50
+
51
+ def empty?
52
+ @updates.empty?
53
+ end
54
+
55
+ def size
56
+ @updates.size
57
+ end
58
+
59
+ # add all updates in a repoparts directory
60
+ # by default look in repoparts/
61
+ # otherwise pass the :repoparts_path option
62
+ def read_repoparts(opts={})
63
+ repoparts_path = opts[:repoparts_path] || File.join(@dir, 'repoparts')
64
+ log.info "Reading update parts from #{repoparts_path}"
65
+ Dir[File.join(repoparts_path, 'update-*.xml')].each do |updatefile|
66
+ log.info("`-> adding update #{updatefile}")
67
+ @updates << updatefile
68
+ end
69
+ # end of directory iteration
70
+ end
71
+
72
+ # generates a patch from a list of package names
73
+ # it compares the last version of those package names
74
+ # with their previous ones
75
+ #
76
+ # outputdir is the directory where to save the patch to.
77
+ def generate_update(packages, outputdir)
78
+
79
+ # make a hash name -> array of packages
80
+ log.info "Generating update part to #{outputdir} for packages #{packages.join(", ")}"
81
+ package_index = {}
82
+
83
+ # look all rpms in the old packages base directory plus
84
+ # the ones in the current one
85
+ rpmfiles = [ Dir["#{@dir}/**/*.rpm"], @basedir.nil? ? [] : Dir["#{@basedir}/**/*.rpm"]].flatten
86
+ log.info "`-> #{rpmfiles.size} rpm packages"
87
+ # reject unwanted files
88
+ rpmfiles.reject! do |rpmfile|
89
+ reject = false
90
+ # reject all delta and src rpms
91
+ reject = true if rpmfile =~ /\.delta\.rpm$|\.src\.rpm$/
92
+ # now reject all the packages for which the rpm file name
93
+ # does not match the name of the requested packages
94
+ # so if packages is A, B and we have C-1.0.rpm it does not
95
+ # match either /A/ nor /B/ so we reject it.
96
+ reject = true if packages.select { |pkg| rpmfile =~ /#{pkg.gsub(/\+/, "\\\\+")}/ }.empty?
97
+ reject
98
+ end
99
+
100
+ log.info "`-> #{rpmfiles.size} rpm packages were not discarded"
101
+ log.debug " #{rpmfiles.map { |x| File.basename(x) }.join(', ')}"
102
+
103
+ # now index all rpms per package name in a hash table
104
+ # which goes from name => list of versions
105
+ rpmfiles.each do |rpmfile|
106
+ rpm = PackageId.new(rpmfile)
107
+ # now that we have the real name, reject if it is not part
108
+ # of the requested packages to generate updates for
109
+ next if not packages.include?(rpm.name)
110
+
111
+ package_index[rpm.name] = Array.new if not package_index.has_key?(rpm.name)
112
+ # add the rpm if there is no other rpm with the same version
113
+ package_index[rpm.name] << rpm if not package_index[rpm.name].select { |x| x.version == rpm.version && x.name == rpm.name }.first
114
+ end
115
+
116
+ log.info "`-> indexed #{package_index.size} unique packages from #{rpmfiles.size} rpms"
117
+ log.debug " #{package_index.keys.join(', ')}"
118
+ # do our package hash include every package?
119
+ packages.reject! do |pkg|
120
+ reject = false
121
+ if not package_index.has_key?(pkg)
122
+ log.warn "`-> the package '#{pkg}' is not available in the repository."
123
+ reject = true
124
+ end
125
+ reject
126
+ end
127
+
128
+ update = Update.new
129
+
130
+ packages.each do |pkgname|
131
+ pkglist = package_index[pkgname]
132
+ log.info "`-> #{pkglist.size} versions for '#{pkgname}'"
133
+ log.debug " #{package_index[pkgname].map {|x| x.version}.join(", ")}"
134
+ # sort them by version
135
+ pkglist.sort! { |a,b| a.version <=> b.version }
136
+ pkglist.reverse!
137
+ # now that the list is sorted, the new rpm is the first
138
+
139
+ # if there is only one package then we don't need changelog
140
+ if pkglist.size > 1
141
+ # we know that there are no duplicate versions so we can
142
+ # take the first and the second
143
+ first = pkglist.shift
144
+ second = pkglist.shift
145
+ diff = first.changelog - second.changelog || []
146
+ #diff = first.changelog[0, first.changelog.size - second.changelog.size] || []
147
+ log.info "`-> found change #{first.ident} and #{second.ident}."
148
+
149
+ log.info "`-> '#{pkgname}' has #{diff.size} change entries (#{first.changelog.size}/#{second.changelog.size})"
150
+ update.packages << first
151
+ diff.each do |entry|
152
+ update.description << entry.text << "\n"
153
+ end
154
+ else
155
+ # jump to next pkgname
156
+ next
157
+ end
158
+
159
+ end
160
+
161
+ # do not save it if there are no packages
162
+ if update.empty?
163
+
164
+ end
165
+
166
+ # before writing the update, figure out more
167
+ # information
168
+ update.smart_fill_blank_fields
169
+
170
+ log.info "`-> update has #{update.references.size} references"
171
+ log.debug " #{update.references.join(',')} "
172
+
173
+ filename = ""
174
+
175
+ FileUtils.mkdir_p outputdir
176
+ # increase version until version is available
177
+ while ( File.exists?(filename = File.join(outputdir, update.suggested_filename + ".xml") ))
178
+ update.version += 1
179
+ end
180
+ log.info "Saving update part to '#{filename}'."
181
+
182
+ File.open(filename, 'w') do |f|
183
+ update.write(f)
184
+ end
185
+ end
186
+
187
+ # splits the updateinfo file into serveral update files
188
+ # it writes those files into outputdir
189
+ # output filenames will be id+_splited_version.xml
190
+ # where id is the update id
191
+ # version is incremented when there is others update files
192
+ # with the same id
193
+ #
194
+ # outputdir is the directory where to save the patch to.
195
+ def split_updates(outputdir)
196
+ FileUtils.mkdir_p outputdir
197
+ updateinfofile = File.join(@dir, metadata_filename)
198
+
199
+ # we can't split without an updateinfo file
200
+ raise "#{updateinfofile} does not exist" if not File.exist?(updateinfofile)
201
+ Zlib::GzipReader.open(updateinfofile) do |gz|
202
+ document = REXML::Document.new(gz)
203
+ root = document.root
204
+ root.each_element("update") do |updateElement|
205
+ id = nil
206
+ updateElement.each_element("id") do |elementId|
207
+ id = elementId.text
208
+ end
209
+ if id == nil
210
+ log.warning 'No id found. Setting id to NON_ID_FOUND'
211
+ id = 'NON_ID_FOUND'
212
+ end
213
+ version = 0
214
+ updatefilename = ""
215
+ while ( File.exists?(updatefilename = File.join(outputdir, "update-#{id}_splited_#{version.to_s}.xml") ) )
216
+ version += 1
217
+ end
218
+ log.info "Saving update part to '#{updatefilename}'."
219
+ File.open(updatefilename, 'w') do |updatefile|
220
+ updatefile << updateElement
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ # write a update out
227
+ def write(file)
228
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
229
+ builder.instruct!
230
+ xml = builder.updates do |b|
231
+ @updates.each do |update|
232
+ File.open(update) do |f|
233
+ file << f.read
234
+ end
235
+ #update.append_to_builder(b)
236
+ end
237
+ end #done builder
238
+ end
239
+
240
+ end
241
+
242
+
243
+ end
244
+ end