Skip to content

Instantly share code, notes, and snippets.

@robinbowes
Created May 26, 2015 16:41
Show Gist options
  • Save robinbowes/0761ddc19a9708e000dc to your computer and use it in GitHub Desktop.
Save robinbowes/0761ddc19a9708e000dc to your computer and use it in GitHub Desktop.
convert_gems_to_rpm
#!/usr/bin/env ruby
require 'trollop'
require 'fpm'
# used to ensure versions extracted from gems are converted to
# semantic versioning-compliant strings before being used
# in RPM requires
require 'semverly'
class Converter
def initialize(logger = Logger.new(STDOUT))
@logger = logger
end
# extra RPM dependencies required by individual packages
# keyed on gem name, each entry is an array
EXTRA_DEPS = {
'nokogiri' => ['libxslt'],
'gpgme' => ['gpgme'],
}
# Sometimes we need to require a pre-existing ruby gem that is not
# installed with a "*rubygem-*" name. An example of this is hiera
# which is installed as pe-hiera on PE, rather than pe-rubygem-hiera
# This hash is used to convert one requirement to another
TRANSLATE_DEPS = {
'rubygem-hiera' => {
'name' => 'hiera',
'epoch' => '',
}
}
# library prefix for the target packages
DST_LIB_DIR = {
'puppet' => '/opt/puppet/lib/ruby/gems/1.9.1',
'ruby193' => '/opt/rh/ruby193/root/usr/share/gems',
'system' => '/usr/share/gems',
}
# name prefix for the target packages
RPM_RUBYGEM_PREFIX = {
'puppet' => 'pe-',
'ruby193' => 'ruby193-',
'system' => '',
}
# location of the gem binary to use
GEM_BIN = {
'puppet' => '/opt/puppet/bin/gem',
'ruby193' => '/opt/rh/ruby193/root/usr/bin/gem',
'system' => '/usr/bin/gem'
}
def convert(
src_gem,
target,
iteration,
epoch
)
# get just the filename from the supplied full-path
gem_file = File.basename(src_gem)
# get the name of the gem, ie. strip the version and .gem
if gem_file =~ /(.*)-(?:[0-9]+\.)+[0-9]+\.gem/
gem_name = $1
@logger.debug "gem name: #{gem_name}"
else
@logger.warn "Couldn't extract gem name from: #{src_gem}"
return
end
dst_lib_dir = DST_LIB_DIR[target]
prefix = RPM_RUBYGEM_PREFIX[target]
extra_deps = EXTRA_DEPS[gem_name] || []
rpm_name = "#{prefix}rubygem-#{gem_name}"
@logger.debug "rpm name: #{rpm_name}"
# this is needed to prevent the stupid nokogiri stupid build process
# from stupidly building & bundling the entire libxslt and libxml2
# libraries
ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] = "1"
# And the same thing here for gpgme
ENV['RUBY_GPGME_USE_SYSTEM_LIBRARIES'] = "1"
package = FPM::Package::Gem.new
package.input(src_gem)
# get the dependencies from the gem
dependencies = package.dependencies
@logger.debug "gem dependencies: #{dependencies}"
# clear dependencies from the gem (we will add them manually later)
package.dependencies = []
# make sure we use the correct gem bin
package.attributes[:gem_gem] = GEM_BIN[target]
@logger.debug "using gem binary from #{GEM_BIN[target]}"
# convert the ruby deps to rpm deps (using code copied from fpm)
# but modified to include epoch
fixed_deps = []
dependencies.each do |dep|
@logger.debug "Processing dependency: #{dep}"
name, op, version = dep.split(/\s+/)
@logger.debug "name: [#{name}] op: [#{op}] version: [#{version}]"
sem_ver = SemVer.parse(version)
@logger.debug "semantic version: [#{sem_ver}]"
if TRANSLATE_DEPS.key?(name)
h = TRANSLATE_DEPS[name]
name = h['name']
if h.key?('epoch')
epoch = h['epoch']
end
end
epoch_str = epoch ? "%s:" % epoch : ''
@logger.debug "Using epoch: #{epoch_str}"
name = "#{prefix}#{name}"
@logger.debug "Using package name: #{name}"
if op == "~>"
# ~> x.y means: > x.y and < (x+1).0
# ~> x.y.z means: > x.y.z and < x.(y+1).0
case version.split('.').size
when 1
upper_ver = false
when 2
upper_ver = SemVer.new(sem_ver.major + 1, 0, 0)
when 3
upper_ver = SemVer.new(sem_ver.major, sem_ver.minor + 1, 0)
else
raise Exception.new "Don't know how to deal with ~> #{version}"
end
fixed_deps << "#{name} >= #{epoch_str}#{sem_ver}"
fixed_deps << "#{name} < #{epoch_str}#{upper_ver}" if upper_ver
else
fixed_deps << "#{name} #{op} #{epoch_str}#{sem_ver}"
end
end
# Add in ruby and rubygems deps (with appropriate prefix)
fixed_deps << "#{prefix}ruby"
fixed_deps << "#{prefix}rubygems"
extra_deps.each do |dep|
fixed_deps << dep
end
@logger.debug "RPM dependencies: #{fixed_deps}"
rpm = package.convert(FPM::Package::RPM)
rpm.iteration = iteration
rpm.epoch = epoch
rpm.name = rpm_name
rpm.attributes[:prefix] = dst_lib_dir
# set the rpm deps
rpm.dependencies = fixed_deps
begin
output = "NAME-VERSION-ITERATION.ARCH.rpm"
rpm.output(rpm.to_s(output))
ensure
rpm.cleanup
end
end
end
opts = Trollop::options do
banner <<-EOS
Convert ruby gems to RPMs
The target specifies which ruby to use:
* system - install to /usr/lib64/ruby/gems, prefix: rubygem-
* puppet - install to /opt/puppet/lib/ruby/gems, prefix: pe-rubygem-
Usage:
convert_gems_to_rpms.rb [options] <filenames>+
where [options] are:
EOS
opt :epoch, 'RPM epoch to use. Defaults to 2 to trump EPEL.', :default => "2"
opt :release, 'RPM release to create', :default => "1"
opt :target, 'which ruby to use - system, ruby193 (SCL), puppet', :default => 'system'
opt :verbose, 'Show info-level logging'
opt :debug, 'Show debug-level logging'
end
# Make sure we have a sane umask
File.umask(0022)
logger = Cabin::Channel.get
logger.subscribe(STDOUT)
if opts[:debug]
logger.level = :debug
elsif opts[:verbose]
logger.level = :info
else
logger.level = :warn
end
ARGV.each{ |src_gem|
Converter.new(logger).convert(src_gem, opts[:target], opts[:release], opts[:epoch])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment