simp-rake-helpers 4.1.1 → 5.0.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.
@@ -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