simp-rake-helpers 4.1.1 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -231,16 +231,19 @@ class Simp::Rake::Pupmod::Helpers < ::Rake::TaskLib
231
231
  if files_changed.empty?
232
232
  puts " No new tag required: No significant files have changed since '#{last_tag}' tag"
233
233
  else
234
- # determine latest CHANGELOG version
235
- line = IO.readlines('CHANGELOG')[0]
236
- match = line.match(/^\*\s+((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{2} \d{4})\s+(.+<.+>)(?:\s+|\s*-\s*)?(\d+\.\d+\.\d+)/)
237
- unless match
238
- fail("ERROR: Invalid CHANGELOG entry. Unable to extract version from '#{line}'")
239
- end
240
-
241
- changelog_version = match[3]
242
- unless module_version == changelog_version
243
- fail("ERROR: Version mismatch. module version=#{module_version} changelog version=#{changelog_version}")
234
+ unless ignore_owner
235
+ # determine latest version from CHANGELOG, which will present
236
+ # for all SIMP Puppet modules
237
+ line = IO.readlines('CHANGELOG')[0]
238
+ match = line.match(/^\*\s+((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{2} \d{4})\s+(.+<.+>)(?:\s+|\s*-\s*)?(\d+\.\d+\.\d+)/)
239
+ unless match
240
+ fail("ERROR: Invalid CHANGELOG entry. Unable to extract version from '#{line}'")
241
+ end
242
+
243
+ changelog_version = match[3]
244
+ unless module_version == changelog_version
245
+ fail("ERROR: Version mismatch. module version=#{module_version} changelog version=#{changelog_version}")
246
+ end
244
247
  end
245
248
 
246
249
  cmp_result = Puppet::Util::Package::versioncmp(module_version, last_tag)
data/lib/simp/rpm.rb CHANGED
@@ -1,14 +1,19 @@
1
1
  require 'securerandom'
2
+ require 'puppet/util'
2
3
 
3
4
  module Simp
4
- # Simp::RPM represents a single package that is built and packaged by the Simp team.
5
+ # An Simp::RPM instance represents RPM metadata extracted from an
6
+ # RPM or an RPM spec file.
7
+ #
8
+ # Simp::RPM also contains class methods that are useful for
9
+ # processing RPMs in the SIMP build process.
5
10
  class Simp::RPM
6
11
  require 'expect'
7
12
  require 'pty'
8
13
  require 'rake'
9
14
 
10
15
  @@gpg_keys = Hash.new
11
- attr_accessor :basename, :version, :release, :full_version, :name, :sources, :verbose
16
+ attr_reader :verbose, :packages
12
17
 
13
18
  if Gem.loaded_specs['rake'].version >= Gem::Version.new('0.9')
14
19
  def self.sh(args)
@@ -16,26 +21,224 @@ module Simp
16
21
  end
17
22
  end
18
23
 
19
- # Constructs a new Simp::RPM object. Requires the path to the spec file
20
- # from which information will be gathered.
24
+ # Constructs a new Simp::RPM object. Requires the path to the spec file, or
25
+ # RPM, from which information will be gathered.
26
+ #
27
+ # When the information is from a spec file, multiple
28
+ # packages may exist.
29
+ #
30
+ # The following information will be retrieved per package:
21
31
  #
22
- # The following information will be retreived:
23
32
  # [basename] The name of the package (as it would be queried in yum)
24
33
  # [version] The version of the package
25
34
  # [release] The release version of the package
26
- # * NOTE: If this is a 'spec' file, it will stop on the first '%'
27
- # encountered!
28
35
  # [full_version] The full version of the package: [version]-[release]
29
36
  # [name] The full name of the package: [basename]-[full_version]
37
+ # [arch] The machine architecture of the package
38
+ # [signature] The signature key of the package, if it exists. Will not
39
+ # apply when +rpm_source+ is an RPM spec file.
40
+ # [rpm_name] The full name of the rpm
30
41
  def initialize(rpm_source)
31
- info = Simp::RPM.get_info(rpm_source)
32
- @basename = info[:name]
33
- @version = info[:version]
34
- @release = info[:release]
35
- @full_version = info[:full_version]
36
- @name = "#{@basename}-#{@full_version}"
37
- @sources = Array.new
38
- @verbose = false
42
+ update_rpmmacros
43
+
44
+ # Simp::RPM.get_info returns a Hash or an Array of Hashes.
45
+ # Steps below prevent single Hash from implicitly being converted
46
+ # to Array using Hash.to_a.
47
+ info_array = []
48
+ info_array << Simp::RPM.get_info(rpm_source)
49
+ info_array.flatten!
50
+
51
+ @info = {}
52
+ info_array.each do |package_info|
53
+ @info[package_info[:basename]] = package_info
54
+ end
55
+
56
+ @packages = @info.keys
57
+ end
58
+
59
+ # @returns The RPM '.dist' of the system. 'nil' will be will be returned if
60
+ # the dist is not found.
61
+ def self.system_dist
62
+ # We can only have one of these
63
+ unless defined?(@@system_dist)
64
+ dist = %x(rpm -E '%{dist}' 2> /dev/null).strip.split('.')
65
+
66
+ if dist.size > 1
67
+ @@system_dist = '.' + dist[1]
68
+ else
69
+ @@system_dist = nil
70
+ end
71
+ end
72
+
73
+ return @@system_dist
74
+ end
75
+
76
+ def system_dist
77
+ return Simp::RPM.system_dist
78
+ end
79
+
80
+ # Work around the silliness with 'centos' being tacked onto things via the
81
+ # 'dist' flag
82
+ def update_rpmmacros
83
+ unless defined?(@@macros_updated)
84
+
85
+ # Workaround for CentOS system builds
86
+ dist = system_dist
87
+ dist_macro = %(%dist #{dist})
88
+
89
+ rpmmacros = [dist_macro]
90
+
91
+ rpmmacros_file = File.join(ENV['HOME'], '.rpmmacros')
92
+
93
+ if File.exist?(rpmmacros_file)
94
+ rpmmacros = File.read(rpmmacros_file).split("\n")
95
+
96
+ dist_index = rpmmacros.each_index.select{|i| rpmmacros[i] =~ /^%dist\s+/}.first
97
+
98
+ if dist_index
99
+ rpmmacros[dist_index] = dist_macro
100
+ else
101
+ rpmmacros << dist_macro
102
+ end
103
+ end
104
+
105
+ File.open(rpmmacros_file, 'w') do |fh|
106
+ fh.puts rpmmacros.join("\n")
107
+ fh.flush
108
+ end
109
+
110
+ @@macros_updated = true
111
+ end
112
+ end
113
+
114
+ # @returns The name of the package (as it would be queried in yum)
115
+ #
116
+ # @fails if package is invalid
117
+ def basename(package=@packages.first)
118
+ valid_package?(package)
119
+ @info[package][:basename]
120
+ end
121
+
122
+ # @returns The version of the package
123
+ #
124
+ # @fails if package is invalid
125
+ def version(package=@packages.first)
126
+ valid_package?(package)
127
+ @info[package][:version]
128
+ end
129
+
130
+ # @returns The release version of the package
131
+ #
132
+ # @fails if package is invalid
133
+ def release(package=@packages.first)
134
+ valid_package?(package)
135
+ @info[package][:release]
136
+ end
137
+
138
+ # @returns The full version of the package: [version]-[release]
139
+ #
140
+ # @fails if package is invalid
141
+ def full_version(package=@packages.first)
142
+ valid_package?(package)
143
+ @info[package][:full_version]
144
+ end
145
+
146
+ # @returns The full name of the package: [basename]-[full_version]
147
+ # @fails if package is invalid
148
+ def name(package=@packages.first)
149
+ valid_package?(package)
150
+ @info[package][:name]
151
+ end
152
+
153
+ # @returns The machine architecture of the package
154
+ #
155
+ # @fails if package is invalid
156
+ def arch(package=@packages.first)
157
+ valid_package?(package)
158
+ @info[package][:arch]
159
+ end
160
+
161
+ # @returns The signature key of the package, if it exists or nil
162
+ # otherwise. Will always be nil when the information for this
163
+ # object was derived from an RPM spec file.
164
+ #
165
+ # @fails if package is invalid
166
+ def signature(package=@packages.first)
167
+ valid_package?(package)
168
+ @info[package][:signature]
169
+ end
170
+
171
+ # @returns The full name of the RPM
172
+ #
173
+ # @fails if package is invalid
174
+ def rpm_name(package=@packages.first)
175
+ valid_package?(package)
176
+ @info[package][:rpm_name]
177
+ end
178
+
179
+ # @returns Whether or not the package has a `dist` tag
180
+ #
181
+ # @fails if package is invalid
182
+ def has_dist_tag?(package=@packages.first)
183
+ valid_package?(package)
184
+ @info[package][:has_dist_tag]
185
+ end
186
+
187
+ # @returns The `dist` of the package. If no `dist` is found, returns the
188
+ # `dist` of the OS itself. Logic should check both `has_dist_tag?` and
189
+ # `dist`
190
+ #
191
+ # @fails if package is invalid
192
+ def dist(package=@packages.first)
193
+ valid_package?(package)
194
+ @info[package][:dist]
195
+ end
196
+
197
+ # Returns whether or not the current RPM package is
198
+ # newer than the passed RPM.
199
+ #
200
+ # Uses the first package in the package list as the
201
+ # current RPM package.
202
+ def newer?(other_rpm)
203
+ package_newer?(@packages.first, other_rpm)
204
+ end
205
+
206
+ # Returns whether or not the current RPM sub-package is
207
+ # newer than the passed RPM.
208
+ def package_newer?(package, other_rpm)
209
+ valid_package?(package)
210
+ return true if other_rpm.nil? || other_rpm.empty?
211
+
212
+ unless other_rpm.match(%r(\.rpm$))
213
+ raise ArgumentError.new("You must pass valid RPM name! Got: '#{other_rpm}'")
214
+ end
215
+
216
+ if File.readable?(other_rpm)
217
+ other_full_version = Simp::RPM.get_info(other_rpm)[:full_version]
218
+ else
219
+ # determine RPM info in a hacky way, ASSUMING, the other RPM has the
220
+ # same basename and arch
221
+ other_full_version = other_rpm.gsub(/#{package}\-/,'').gsub(/.rpm$/,'')
222
+ package_arch = arch(package)
223
+ unless package_arch.nil? or package_arch.empty?
224
+ other_full_version.gsub!(/.#{package_arch}/,'')
225
+ end
226
+ end
227
+
228
+ begin
229
+ # Puppet::Util::Package::versioncmp can handle simp-doc-UNKNOWN-0.el7, whereas
230
+ # Gem::Version can't
231
+ return Puppet::Util::Package::versioncmp(full_version(package), other_full_version) > 0
232
+
233
+ rescue ArgumentError, NoMethodError
234
+ fail("Could not compare RPMs '#{rpm_name(package)}' and '#{other_rpm}'")
235
+ end
236
+ end
237
+
238
+ def valid_package?(package)
239
+ unless @packages.include?(package)
240
+ raise ArgumentError.new("'#{package}' is not a valid sub-package")
241
+ end
39
242
  end
40
243
 
41
244
  # Copies specific content from one directory to another.
@@ -78,76 +281,156 @@ module Simp
78
281
  FileUtils.rm_f([outfile, errfile])
79
282
  end
80
283
 
81
- # Parses information, such as the version, from the given specfile or RPM
82
- # into a hash.
284
+ # Parses information, such as the version, from the given specfile
285
+ # or RPM into a hash.
83
286
  #
84
- # Can take an optional mock hash that should have the following structure:
85
- # {
86
- # :command => The actual mock command to run
87
- # :rpm_extras => Extra arguments to pass to RPM. This will probably be a
88
- # reference to the spec file itself
89
- # }
90
- def self.get_info(rpm_source, mock_hash=nil)
91
- info = {
92
- :has_dist_tag => false
93
- }
287
+ # If the information from only single RPM is extracted, returns a
288
+ # single Hash with the following possible keys:
289
+ # :has_dist_tag = a boolean indicating whether the RPM release
290
+ # has a distribution field; only evaluated when
291
+ # rpm_source is a spec file, otherwise false
292
+ # :basename = The name of the package (as it would be
293
+ # queried in yum)
294
+ # :version = The version of the package
295
+ # :release = The release version of the package
296
+ # :arch = The machine architecture of the package
297
+ # :full_version = The full version of the package:
298
+ # <version>-<release>
299
+ # :name = The full name of the package:
300
+ # <basename>-<full_version>
301
+ # :rpm_name = The full name of the RPM:
302
+ # <basename>-<full_version>.<arch>.rpm
303
+ # :signature = RPM signature key id; only present if
304
+ # rpm_source is an RPM and the RPM is signed
305
+ #
306
+ # If the information from more than one RPM is extracted, as is the case
307
+ # when a spec file specifies sub-packages, returns an Array of Hashes.
308
+ #
309
+ def self.get_info(rpm_source)
310
+ raise "Error: unable to read '#{rpm_source}'" unless File.readable?(rpm_source)
94
311
 
95
- rpm_cmd = "rpm -q --queryformat '%{NAME} %{VERSION} %{RELEASE} %{ARCH}\n'"
96
-
97
- if mock_hash
98
- # Suppression of error messages is a hack for the following
99
- # scenario:
100
- # * The RPM spec file has an invalid date in its %changelog.
101
- # * The 'bogus date' warning message from rpmbuild
102
- # is sent to stderr, while RPM name, version, and release
103
- # info is sent to stdout.
104
- # * mock combines stdout and stderr in the command run.
105
- # * The 'bogus date' warning message is parsed to generate
106
- # the RPM info, instead of the RPM info message.
107
- rpm_cmd = mock_hash[:command] + ' ' + '"' + rpm_cmd + ' ' + mock_hash[:rpm_extras] + ' 2>/dev/null"'
108
- end
312
+ info_array = []
313
+ common_info = {
314
+ :has_dist_tag => false,
315
+ :dist => system_dist
316
+ }
109
317
 
110
- if File.readable?(rpm_source)
111
- if File.read(rpm_source).include?('%{?dist}')
112
- info[:has_dist_tag] = true
113
- end
318
+ rpm_version_query = %(rpm -q --queryformat '%{NAME} %{VERSION} %{RELEASE} %{ARCH}\n' 2>/dev/null)
114
319
 
115
- if rpm_source.split('.').last == 'rpm'
116
- results = execute("#{rpm_cmd} -p #{rpm_source}")
117
- elsif mock_hash
118
- results = execute("#{rpm_cmd}")
320
+ rpm_signature_query = %(rpm -q --queryformat '%|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:{%|SIGGPG?{%{SIGGPG:pgpsig}}:{%|SIGPGP?{%{SIGPGP:pgpsig}}:{(none)}|}|}|}|\n\')
119
321
 
120
- if info[:has_dist_tag]
121
- info[:dist_tag] = execute(%(#{mock_hash[:command]} --chroot 'rpm --eval "%{dist}"' 2>/dev/null))[:stdout].strip
322
+ source_is_rpm = rpm_source.split('.').last == 'rpm'
323
+ if source_is_rpm
324
+ dist_info = rpm_source.split('-').last.split('.')[1..-3]
122
325
 
123
- info[:dist_tag] = nil if (info[:dist_tag][0].chr == '%')
124
- end
125
- else
126
- results = execute("#{rpm_cmd} --specfile #{rpm_source}")
326
+ unless dist_info.empty?
327
+ common_info[:has_dist_tag] = true
328
+ common_info[:dist] = '.' + dist_info.first
127
329
  end
128
330
 
129
- if results[:exit_status] != 0
130
- raise <<-EOE
331
+ elsif File.read(rpm_source).include?('%{?dist}')
332
+ common_info[:has_dist_tag] = true
333
+ end
334
+
335
+ if source_is_rpm
336
+ query_source = "-p #{rpm_source}"
337
+ version_results = execute("#{rpm_version_query} #{query_source}")
338
+ signature_results = execute("#{rpm_signature_query} #{query_source}")
339
+ else
340
+ query_source = "--specfile #{rpm_source}"
341
+ version_results = execute("#{rpm_version_query} #{query_source}")
342
+ signature_results = nil
343
+ end
344
+
345
+ if version_results[:exit_status] != 0
346
+ raise <<-EOE
131
347
  #{indent('Error getting RPM info:', 2)}
132
- #{indent(results[:stderr].strip, 5)}
133
- #{indent("Run '#{rpm_cmd.gsub("\n",'\\n')} --specfile #{rpm_source}' to recreate the issue.", 2)}
348
+ #{indent(version_results[:stderr].strip, 5)}
349
+ #{indent("Run '#{rpm_version_query.gsub("\n",'\\n')} #{query_source}' to recreate the issue.", 2)}
134
350
  EOE
135
- end
351
+ end
136
352
 
137
- info[:name], info[:version], info[:release], info[:arch] = results[:stdout].strip.split("\n").first.split(' ')
138
- else
139
- raise "Error: unable to read '#{rpm_source}'"
353
+ unless signature_results.nil?
354
+ if signature_results[:exit_status] != 0
355
+ raise <<-EOE
356
+ #{indent('Error getting RPM signature:', 2)}
357
+ #{indent(signature_results[:stderr].strip, 5)}
358
+ #{indent("Run '#{rpm_signature_query.gsub("\n",'\\n')} #{query_source}' to recreate the issue.", 2)}
359
+ EOE
360
+ else
361
+ signature = signature_results[:stdout].strip
362
+ end
140
363
  end
141
364
 
142
- info[:full_version] = "#{info[:version]}-#{info[:release]}"
365
+ version_results[:stdout].strip.lines.each do |line|
366
+ info = common_info.dup
367
+ parts = line.split(' ')
143
368
 
144
- return info
369
+ info[:basename], info[:version], info[:release], info[:arch] = parts
370
+ info[:signature] = signature unless signature.nil? or signature.include?('none')
371
+ info[:full_version] = "#{info[:version]}-#{info[:release]}"
372
+ info[:name] = "#{info[:basename]}-#{info[:full_version]}"
373
+ info[:rpm_name] = "#{info[:name]}.#{info[:arch]}.rpm"
374
+
375
+ info_array << info
376
+ end
377
+
378
+ if info_array.size == 1
379
+ return info_array[0]
380
+ else
381
+ # will only happen when source is spec file and that spec file
382
+ # specifies sub-packages
383
+ return info_array
384
+ end
145
385
  end
146
386
 
147
387
  def self.indent(message, indent_length)
148
388
  message.split("\n").map {|line| ' '*indent_length + line }.join("\n")
149
389
  end
150
390
 
391
+ def self.create_rpm_build_metadata(project_dir, srpms=nil, rpms=nil)
392
+ last_build = {
393
+ 'git_hash' => %x(git rev-list --max-count=1 HEAD).chomp,
394
+ 'srpms' => {},
395
+ 'rpms' => {}
396
+ }
397
+
398
+ Dir.chdir(File.join(project_dir, 'dist')) do
399
+ if srpms.nil? or rpms.nil?
400
+ all_rpms = Dir.glob('*.rpm')
401
+ srpms = Dir.glob('src.rpm')
402
+ rpms = all_rpms - srpms
403
+ end
404
+
405
+ srpms.each do |srpm|
406
+ file_stat = File.stat(srpm)
407
+
408
+ last_build['srpms'][File.basename(srpm)] = {
409
+ 'metadata' => Simp::RPM.get_info(srpm),
410
+ 'size' => file_stat.size,
411
+ 'timestamp' => file_stat.ctime,
412
+ 'path' => File.absolute_path(srpm)
413
+ }
414
+ end
415
+
416
+ rpms.each do |rpm|
417
+ file_stat = File.stat(rpm)
418
+
419
+ last_build['rpms'][File.basename(rpm)] = {
420
+ 'metadata' => Simp::RPM.get_info(rpm),
421
+ 'size' => file_stat.size,
422
+ 'timestamp' => file_stat.ctime,
423
+ 'path' => File.absolute_path(rpm)
424
+ }
425
+ end
426
+
427
+ FileUtils.mkdir_p(File.join(project_dir, 'dist', 'logs'))
428
+ File.open('logs/last_rpm_build_metadata.yaml','w') do |fh|
429
+ fh.puts(last_build.to_yaml)
430
+ end
431
+ end
432
+ end
433
+
151
434
  # Loads metadata for a GPG key. The GPG key is to be used to sign RPMs. The
152
435
  # value of gpg_key should be the full path of the directory where the key
153
436
  # resides. If the metadata cannot be found, then the user will be prompted