enhancerepo 0.4.1

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.
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