Class: Simp::RPM

Inherits:
Object
  • Object
show all
Defined in:
lib/simp/rpm.rb

Overview

Note:

Set the environment variable ‘SIMP_RPM_dist` to ensure all packages use a particular dist.

An Simp::RPM instance represents RPM metadata extracted from an RPM or an RPM spec file.

Simp::RPM also contains class methods that are useful for processing RPMs in the SIMP build process.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rpm_source) ⇒ RPM

Constructs a new Simp::RPM object. Requires the path to the spec file, or RPM, from which information will be gathered.

When the information is from a spec file, multiple packages may exist.

The following information will be retrieved per package:

basename

The name of the package (as it would be queried in yum)

version

The version of the package

release

The release version of the package

full_version

The full version of the package: [version]-

name

The full name of the package: [basename]-

arch

The machine architecture of the package

signature

The signature key of the package, if it exists. Will not

apply when +rpm_source+ is an RPM spec file.
rpm_name

The full name of the rpm



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/simp/rpm.rb', line 46

def initialize(rpm_source)
  @verbose = ENV.fetch('SIMP_RPM_verbose','no') =='yes'

  update_rpmmacros

  # Simp::RPM.get_info returns a Hash or an Array of Hashes.
  # Steps below prevent single Hash from implicitly being converted
  # to Array using Hash.to_a.
  info_array = []
  info_array << Simp::RPM.get_info(rpm_source)
  info_array.flatten!

  @info = {}
  info_array.each do |package_info|
    @info[package_info[:basename]] = package_info
  end

  @packages = @info.keys

  if @verbose
    require 'pp'
    puts "== Simp::RPM @packages"
    puts @packages.pretty_inspect
  end
end

Instance Attribute Details

#lua_debugObject (readonly)

Returns the value of attribute lua_debug.



17
18
19
# File 'lib/simp/rpm.rb', line 17

def lua_debug
  @lua_debug
end

#packagesObject (readonly)

Returns the value of attribute packages.



17
18
19
# File 'lib/simp/rpm.rb', line 17

def packages
  @packages
end

#verboseObject (readonly)

Returns the value of attribute verbose.



17
18
19
# File 'lib/simp/rpm.rb', line 17

def verbose
  @verbose
end

Class Method Details

.copy_wo_vcs(start_dir, src, dest, dereference = true) ⇒ Object

Copies specific content from one directory to another.

start_dir

the root directory where the original files are located within

src

a pattern given to find(1) to match against the desired files to copy

dest

the destination directory to receive the copies



275
276
277
278
279
280
281
282
283
284
285
# File 'lib/simp/rpm.rb', line 275

def self.copy_wo_vcs(start_dir, src, dest, dereference=true)
  if dereference.nil? || dereference
    dereference = "--dereference"
  else
    dereference = ""
  end

  Dir.chdir(start_dir) do
    sh %{find #{src} \\( -path "*/.svn" -a -type d -o -path "*/.git*" \\) -prune -o -print | cpio -u --warning none --quiet --make-directories #{dereference} -p "#{dest}" 2>&1 > /dev/null}
  end
end

.create_rpm_build_metadata(project_dir, srpms = nil, rpms = nil) ⇒ Object



455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# File 'lib/simp/rpm.rb', line 455

def self.(project_dir, srpms=nil, rpms=nil)
  require 'yaml'

  last_build = {
    'git_hash' => %x(git rev-list --max-count=1 HEAD).chomp,
    'srpms'    => {},
    'rpms'     => {}
  }

  Dir.chdir(File.join(project_dir, 'dist')) do
    if srpms.nil? or rpms.nil?
      all_rpms = Dir.glob('*.rpm')
      srpms = Dir.glob('src.rpm')
      rpms = all_rpms - srpms
    end

    srpms.each do |srpm|
      file_stat = File.stat(srpm)

      last_build['srpms'][File.basename(srpm)] = {
        'metadata'  => Simp::RPM.get_info(srpm),
        'size'      => file_stat.size,
        'timestamp' => file_stat.ctime,
        'path'      => File.absolute_path(srpm)
      }
    end

    rpms.each do |rpm|
      file_stat = File.stat(rpm)

      last_build['rpms'][File.basename(rpm)] = {
        'metadata' => Simp::RPM.get_info(rpm),
        'size'      => file_stat.size,
        'timestamp' => file_stat.ctime,
        'path'     => File.absolute_path(rpm)
       }
    end

    FileUtils.mkdir_p(File.join(project_dir, 'dist', 'logs'))
    File.open('logs/last_rpm_build_metadata.yaml','w') do |fh|
      fh.puts(last_build.to_yaml)
    end
  end
end

.execute(cmd) ⇒ Object

Executes a command and returns a hash with the exit status, stdout output and stderr output.

cmd

command to be executed



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/simp/rpm.rb', line 290

def self.execute(cmd)
  if @verbose ||= ENV.fetch('SIMP_RPM_verbose','no') =='yes'
    puts "== Simp::RPM.execute(#{cmd})"
    puts "  #{cmd}"
  end

  outfile = File.join('/tmp', "#{ENV['USER']}_#{SecureRandom.hex}")
  errfile = File.join('/tmp', "#{ENV['USER']}_#{SecureRandom.hex}")
  pid = spawn(cmd, :out=>outfile, :err=>errfile)

  begin
    pid,status = Process.wait2(pid)
  rescue Errno::ECHILD
    # process exited before status could be determined
  end

  exit_status = status.nil? ? nil : status.exitstatus
  stdout = IO.read(outfile)
  stderr = IO.read(errfile)

  { :exit_status => exit_status, :stdout => stdout, :stderr => stderr }
ensure
  if @verbose
    puts "    -------- exit_status: #{exit_status}"
    puts "    -------- stdout ",''
    puts File.readlines(outfile).map{|x| "    #{x}"}.join
    puts '',"    -------- stderr ",''
    puts File.readlines(errfile).map{|x| "    #{x}"}.join
  end
  FileUtils.rm_f([outfile, errfile])
end

.get_info(rpm_source) ⇒ Object

Parses information, such as the version, from the given specfile or RPM into a hash.

If the information from only single RPM is extracted, returns a single Hash with the following possible keys:

:has_dist_tag = a boolean indicating whether the RPM release
                 has a distribution field; only evaluated when
                 rpm_source is a spec file, otherwise false
:basename      = The name of the package (as it would be
                 queried in yum)
:version       = The version of the package
:release       = The release version of the package
:arch          = The machine architecture of the package
:full_version  = The full version of the package:
                   <version>-<release>
:name          = The full name of the package:
                   <basename>-<full_version>
:rpm_name      = The full name of the RPM:
                   <basename>-<full_version>.<arch>.rpm
:signature     = RPM signature key id; only present if
                 rpm_source is an RPM and the RPM is signed

If the information from more than one RPM is extracted, as is the case when a spec file specifies sub-packages, returns an Array of Hashes.



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/simp/rpm.rb', line 347

def self.get_info(rpm_source)
  raise "Error: unable to read '#{rpm_source}'" unless File.readable?(rpm_source)

  if ENV['SIMP_RPM_dist']
    target_dist = (ENV['SIMP_RPM_dist'] =~ /^\./) ? ENV['SIMP_RPM_dist'] : ('.' + ENV['SIMP_RPM_dist'])
  else
    target_dist = system_dist
  end

  info_array = []
  common_info = {}

  rpm_version_query = %Q(#{rpm_cmd} -q --queryformat '%{NAME} %{VERSION} %{RELEASE} %{ARCH}\\n')

  rpm_signature_query = %Q(#{rpm_cmd} -q --queryformat '%|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:{%|SIGGPG?{%{SIGGPG:pgpsig}}:{%|SIGPGP?{%{SIGPGP:pgpsig}}:{(none)}|}|}|}|\\n')

  source_is_rpm = rpm_source.split('.').last == 'rpm'
  if source_is_rpm
    dist_info = rpm_source.split('-').last.split('.')[1..-3]

    unless dist_info.empty?
      common_info[:has_dist_tag] = true
      common_info[:dist] = '.' + dist_info.first
    else
      common_info[:has_dist_tag] = false
      common_info[:dist] = target_dist
    end
  elsif File.read(rpm_source).include?('%{?dist}')
    common_info[:dist] =target_dist
    common_info[:has_dist_tag] = true
  else
    common_info[:has_dist_tag] = false
    common_info[:dist]     = target_dist
  end

  unless source_is_rpm
    macros = {
      'dist' => target_dist
    }
    macros.each do |k,v|
      rpm_version_query += %Q[ -D '#{k} #{v}']
    end

  end


  if source_is_rpm
    query_source = "-p #{rpm_source}"
    version_results = execute("#{rpm_version_query} #{query_source}")
    signature_results = execute("#{rpm_signature_query} #{query_source}")
  else
    query_source = "--specfile #{rpm_source}"
    version_results = execute("#{rpm_version_query} #{query_source}")
    signature_results = nil
  end

  if version_results[:exit_status] != 0
    raise <<~EOE
      #{indent('Error getting RPM info for #{query_source}:', 2)}
      #{indent(version_results[:stderr].strip, 5)}
      #{indent("Run '#{rpm_version_query.gsub("\n",'\\n')} #{query_source}' to recreate the issue.", 2)}
    EOE
  end

  unless signature_results.nil?
    if signature_results[:exit_status] != 0
      raise <<~EOE
        #{indent('Error getting RPM signature for #{query_source}:', 2)}
        #{indent(signature_results[:stderr].strip, 5)}
        #{indent("Run '#{rpm_signature_query.gsub("\n",'\\n')} #{query_source}' to recreate the issue.", 2)}
      EOE
   else
     signature = signature_results[:stdout].strip
   end
  end

  version_results[:stdout].strip.lines.each do |line|
    info = common_info.dup
    parts = line.split(' ')

    info[:basename], info[:version], info[:release], info[:arch] = parts
    info[:signature]    = signature unless signature.nil? or signature.include?('none')
    info[:full_version] = "#{info[:version]}-#{info[:release]}"
    info[:name]         = "#{info[:basename]}-#{info[:full_version]}"
    info[:rpm_name]     = "#{info[:name]}.#{info[:arch]}.rpm"

    info_array << info
  end

  if @verbose
    puts "== SIMP::RPM.get_info"
    require 'pp'
    pp info_array
  end

  if info_array.size == 1
    return info_array[0]
  else
    # will only happen when source is spec file and that spec file
    # specifies sub-packages
    return info_array
  end
end

.indent(message, indent_length) ⇒ Object



451
452
453
# File 'lib/simp/rpm.rb', line 451

def self.indent(message, indent_length)
   message.split("\n").map {|line| ' '*indent_length + line }.join("\n")
end

.rpm_cmdObject



25
26
27
# File 'lib/simp/rpm.rb', line 25

def self.rpm_cmd
  @rpm_cmd ||= (ENV.fetch('SIMP_RPM_LUA_debug','no') =='yes') ? "rpm -D 'lua_debug 1'" : 'rpm'
end

.sh(args) ⇒ Object



20
21
22
# File 'lib/simp/rpm.rb', line 20

def self.sh(args)
  system args
end

.system_distObject

Note:

This causes problems for ISO builds that target a particular OS if it doesn’t match the host. Set the environment variable ‘SIMP_RPM_dist` to ensure all packages use a particular dist.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/simp/rpm.rb', line 77

def self.system_dist
  # We can only have one of these
  unless defined?(@@system_dist)
    cmd  = %Q(#{rpm_cmd} -E '%{dist}' 2> /dev/null)
    if @verbose
      puts "== Simp::RPM.system_dist"
      puts "   #{cmd} "
    end
    dist = %x{#{cmd}}.strip.split('.')
    puts "  result = '#{dist}'" if @verbose

    if dist.size > 1
      @@system_dist = (dist[1] =~ /^\./) ? dist[1] : ('.' + dist[1])
    else
      @@system_dist = nil
    end
    puts "  @@system_dist = #{@@system_dist ||'nil'}" if @verbose
  end

  return @@system_dist
end

.versionObject

Returns the version of RPM installed on the system



501
502
503
# File 'lib/simp/rpm.rb', line 501

def self.version
  %x{rpm --version}.strip.split.last
end

Instance Method Details

#arch(package = @packages.first) ⇒ Object



184
185
186
187
# File 'lib/simp/rpm.rb', line 184

def arch(package=@packages.first)
  valid_package?(package)
  @info[package][:arch]
end

#basename(package = @packages.first) ⇒ Object



145
146
147
148
# File 'lib/simp/rpm.rb', line 145

def basename(package=@packages.first)
  valid_package?(package)
  @info[package][:basename]
end

#dist(package = @packages.first) ⇒ Object

‘dist` of the OS itself. Logic should check both `has_dist_tag?` and `dist`



220
221
222
223
# File 'lib/simp/rpm.rb', line 220

def dist(package=@packages.first)
  valid_package?(package)
  @info[package][:dist]
end

#full_version(package = @packages.first) ⇒ Object



169
170
171
172
# File 'lib/simp/rpm.rb', line 169

def full_version(package=@packages.first)
  valid_package?(package)
  @info[package][:full_version]
end

#has_dist_tag?(package = @packages.first) ⇒ Boolean

Returns:

  • (Boolean)


210
211
212
213
# File 'lib/simp/rpm.rb', line 210

def has_dist_tag?(package=@packages.first)
  valid_package?(package)
  @info[package][:has_dist_tag]
end

#name(package = @packages.first) ⇒ Object



176
177
178
179
# File 'lib/simp/rpm.rb', line 176

def name(package=@packages.first)
  valid_package?(package)
  @info[package][:name]
end

#newer?(other_rpm) ⇒ Boolean

Returns whether or not the current RPM package is newer than the passed RPM.

Uses the first package in the package list as the current RPM package.

Returns:

  • (Boolean)


230
231
232
# File 'lib/simp/rpm.rb', line 230

def newer?(other_rpm)
  package_newer?(@packages.first, other_rpm)
end

#package_newer?(package, other_rpm) ⇒ Boolean

Returns whether or not the current RPM sub-package is newer than the passed RPM.

Returns:

  • (Boolean)


236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/simp/rpm.rb', line 236

def package_newer?(package, other_rpm)
  valid_package?(package)
  return true if other_rpm.nil? || other_rpm.empty?

  unless other_rpm.match(%r(\.rpm$))
    raise ArgumentError.new("You must pass valid RPM name! Got: '#{other_rpm}'")
  end

  if File.readable?(other_rpm)
    other_full_version = Simp::RPM.get_info(other_rpm)[:full_version]
  else
    # determine RPM info in a hacky way, ASSUMING, the other RPM has the
    # same basename and arch
    other_full_version = other_rpm.gsub(/#{package}\-/,'').gsub(/.rpm$/,'')
    package_arch = arch(package)
    unless package_arch.nil? or package_arch.empty?
      other_full_version.gsub!(/.#{package_arch}/,'')
    end
  end

  begin

    return Gem::Version.new(full_version(package)) > Gem::Version.new(other_full_version)

  rescue ArgumentError, NoMethodError
    fail("Could not compare RPMs '#{rpm_name(package)}' and '#{other_rpm}'")
  end
end

#release(package = @packages.first) ⇒ Object



161
162
163
164
# File 'lib/simp/rpm.rb', line 161

def release(package=@packages.first)
  valid_package?(package)
  @info[package][:release]
end

#rpm_name(package = @packages.first) ⇒ Object



202
203
204
205
# File 'lib/simp/rpm.rb', line 202

def rpm_name(package=@packages.first)
  valid_package?(package)
  @info[package][:rpm_name]
end

#signature(package = @packages.first) ⇒ Object



194
195
196
197
# File 'lib/simp/rpm.rb', line 194

def signature(package=@packages.first)
  valid_package?(package)
  @info[package][:signature]
end

#system_distObject



99
100
101
# File 'lib/simp/rpm.rb', line 99

def system_dist
  return Simp::RPM.system_dist
end

#update_rpmmacrosObject

Work around the silliness with ‘centos’ being tacked onto things via the ‘dist’ flag



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/simp/rpm.rb', line 105

def update_rpmmacros
  unless defined?(@@macros_updated)

    # Workaround for CentOS system builds
    dist           = ENV['SIMP_RPM_dist'] || system_dist
    dist_macro     = %(%dist #{dist})
    rpmmacros      = [dist_macro]
    rpmmacros_file = File.join(ENV['HOME'], '.rpmmacros')

    if File.exist?(rpmmacros_file)
      rpmmacros = File.read(rpmmacros_file).split("\n")

      dist_index = rpmmacros.each_index.select{|i| rpmmacros[i] =~ /^%dist\s+/}.first

      if dist_index
        rpmmacros[dist_index] = dist_macro
      else
        rpmmacros << dist_macro
      end
    end

    File.open(rpmmacros_file, 'w') do |fh|
      fh.puts rpmmacros.join("\n")
      fh.flush
    end

    if @verbose
      puts "== SIMP::RPM#update_rpmmacros:"
      puts "   wrote to '#{rpmmacros_file}': "
      puts "   #{'-'*20}"
      puts rpmmacros.map{|x| "   #{x}\n"}.join
      puts
    end
    @@macros_updated = true
  end
end

#valid_package?(package) ⇒ Boolean

Returns:

  • (Boolean)


265
266
267
268
269
# File 'lib/simp/rpm.rb', line 265

def valid_package?(package)
  unless @packages.include?(package)
    raise ArgumentError.new("'#{package}' is not a valid sub-package")
  end
end

#version(package = @packages.first) ⇒ Object



153
154
155
156
# File 'lib/simp/rpm.rb', line 153

def version(package=@packages.first)
  valid_package?(package)
  @info[package][:version]
end