mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Merge upstream revision of rubygems/rubygems.
This commits includes tiny bugfix and new features listed here: * Add --re-sign flag to cert command by bronzdoc: https://github.com/rubygems/rubygems/pull/2391 * Download gems with threads. by indirect: https://github.com/rubygems/rubygems/pull/1898 git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64769 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
3367daf716
commit
ec6c075702
9 changed files with 165 additions and 80 deletions
|
@ -14,15 +14,16 @@ class Gem::Commands::CertCommand < Gem::Command
|
|||
super 'cert', 'Manage RubyGems certificates and signing settings',
|
||||
:add => [], :remove => [], :list => [], :build => [], :sign => []
|
||||
|
||||
OptionParser.accept OpenSSL::X509::Certificate do |certificate|
|
||||
OptionParser.accept OpenSSL::X509::Certificate do |certificate_file|
|
||||
begin
|
||||
OpenSSL::X509::Certificate.new File.read certificate
|
||||
certificate = OpenSSL::X509::Certificate.new File.read certificate_file
|
||||
rescue Errno::ENOENT
|
||||
raise OptionParser::InvalidArgument, "#{certificate}: does not exist"
|
||||
raise OptionParser::InvalidArgument, "#{certificate_file}: does not exist"
|
||||
rescue OpenSSL::X509::CertificateError
|
||||
raise OptionParser::InvalidArgument,
|
||||
"#{certificate}: invalid X509 certificate"
|
||||
"#{certificate_file}: invalid X509 certificate"
|
||||
end
|
||||
[certificate, certificate_file]
|
||||
end
|
||||
|
||||
OptionParser.accept OpenSSL::PKey::RSA do |key_file|
|
||||
|
@ -42,7 +43,7 @@ class Gem::Commands::CertCommand < Gem::Command
|
|||
end
|
||||
|
||||
add_option('-a', '--add CERT', OpenSSL::X509::Certificate,
|
||||
'Add a trusted certificate.') do |cert, options|
|
||||
'Add a trusted certificate.') do |(cert, _), options|
|
||||
options[:add] << cert
|
||||
end
|
||||
|
||||
|
@ -67,8 +68,9 @@ class Gem::Commands::CertCommand < Gem::Command
|
|||
end
|
||||
|
||||
add_option('-C', '--certificate CERT', OpenSSL::X509::Certificate,
|
||||
'Signing certificate for --sign') do |cert, options|
|
||||
'Signing certificate for --sign') do |(cert, cert_file), options|
|
||||
options[:issuer_cert] = cert
|
||||
options[:issuer_cert_file] = cert_file
|
||||
end
|
||||
|
||||
add_option('-K', '--private-key KEY', OpenSSL::PKey::RSA,
|
||||
|
@ -89,6 +91,11 @@ class Gem::Commands::CertCommand < Gem::Command
|
|||
'Days before the certificate expires') do |days, options|
|
||||
options[:expiration_length_days] = days.to_i
|
||||
end
|
||||
|
||||
add_option('-R', '--re-sign',
|
||||
'Re-signs the certificate from -C with the key from -K') do |resign, options|
|
||||
options[:resign] = resign
|
||||
end
|
||||
end
|
||||
|
||||
def add_certificate certificate # :nodoc:
|
||||
|
@ -114,6 +121,14 @@ class Gem::Commands::CertCommand < Gem::Command
|
|||
build email
|
||||
end
|
||||
|
||||
if options[:resign]
|
||||
re_sign_cert(
|
||||
options[:issuer_cert],
|
||||
options[:issuer_cert_file],
|
||||
options[:key]
|
||||
)
|
||||
end
|
||||
|
||||
sign_certificates unless options[:sign].empty?
|
||||
end
|
||||
|
||||
|
@ -290,6 +305,13 @@ For further reading on signing gems see `ri Gem::Security`.
|
|||
end
|
||||
end
|
||||
|
||||
def re_sign_cert(cert, cert_path, private_key)
|
||||
Gem::Security::Signer.re_sign_cert(cert, cert_path, private_key) do |expired_cert_path, new_expired_cert_path|
|
||||
alert("Your certificate #{expired_cert_path} has been re-signed")
|
||||
alert("Your expired certificate will be located at: #{new_expired_cert_path}")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_email? email
|
||||
|
|
|
@ -27,6 +27,7 @@ require 'rbconfig'
|
|||
# +:backtrace+:: See #backtrace
|
||||
# +:sources+:: Sets Gem::sources
|
||||
# +:verbose+:: See #verbose
|
||||
# +:concurrent_downloads+:: See #concurrent_downloads
|
||||
#
|
||||
# gemrc files may exist in various locations and are read and merged in
|
||||
# the following order:
|
||||
|
@ -43,6 +44,7 @@ class Gem::ConfigFile
|
|||
DEFAULT_BULK_THRESHOLD = 1000
|
||||
DEFAULT_VERBOSITY = true
|
||||
DEFAULT_UPDATE_SOURCES = true
|
||||
DEFAULT_CONCURRENT_DOWNLOADS = 8
|
||||
|
||||
##
|
||||
# For Ruby packagers to set configuration defaults. Set in
|
||||
|
@ -104,6 +106,11 @@ class Gem::ConfigFile
|
|||
|
||||
attr_accessor :verbose
|
||||
|
||||
##
|
||||
# Number of gem downloads that should be performed concurrently.
|
||||
|
||||
attr_accessor :concurrent_downloads
|
||||
|
||||
##
|
||||
# True if we want to update the SourceInfoCache every time, false otherwise
|
||||
|
||||
|
@ -177,6 +184,7 @@ class Gem::ConfigFile
|
|||
@bulk_threshold = DEFAULT_BULK_THRESHOLD
|
||||
@verbose = DEFAULT_VERBOSITY
|
||||
@update_sources = DEFAULT_UPDATE_SOURCES
|
||||
@concurrent_downloads = DEFAULT_CONCURRENT_DOWNLOADS
|
||||
|
||||
operating_system_config = Marshal.load Marshal.dump(OPERATING_SYSTEM_DEFAULTS)
|
||||
platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS)
|
||||
|
@ -200,6 +208,7 @@ class Gem::ConfigFile
|
|||
@path = @hash[:gempath] if @hash.key? :gempath
|
||||
@update_sources = @hash[:update_sources] if @hash.key? :update_sources
|
||||
@verbose = @hash[:verbose] if @hash.key? :verbose
|
||||
@concurrent_downloads = @hash[:concurrent_downloads] if @hash.key? :concurrent_downloads
|
||||
@disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server
|
||||
@sources = @hash[:sources] if @hash.key? :sources
|
||||
|
||||
|
@ -415,6 +424,9 @@ if you believe they were disclosed to a third party.
|
|||
yaml_hash[:update_sources] = @hash.fetch(:update_sources, DEFAULT_UPDATE_SOURCES)
|
||||
yaml_hash[:verbose] = @hash.fetch(:verbose, DEFAULT_VERBOSITY)
|
||||
|
||||
yaml_hash[:concurrent_downloads] =
|
||||
@hash.fetch(:concurrent_downloads, DEFAULT_CONCURRENT_DOWNLOADS)
|
||||
|
||||
yaml_hash[:ssl_verify_mode] =
|
||||
@hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode
|
||||
|
||||
|
|
|
@ -771,30 +771,26 @@ TEXT
|
|||
# return the stub script text used to launch the true Ruby script
|
||||
|
||||
def windows_stub_script(bindir, bin_file_name)
|
||||
rb_config = RbConfig::CONFIG
|
||||
rb_topdir = RbConfig::TOPDIR || File.dirname(rb_config["bindir"])
|
||||
# get ruby executable file name from RbConfig
|
||||
ruby_exe = "#{rb_config['RUBY_INSTALL_NAME']}#{rb_config['EXEEXT']}"
|
||||
|
||||
if File.exist?(File.join bindir, ruby_exe)
|
||||
rb_bindir = RbConfig::CONFIG["bindir"]
|
||||
# All comparisons should be case insensitive
|
||||
if bindir.downcase == rb_bindir.downcase
|
||||
# stub & ruby.exe withing same folder. Portable
|
||||
<<-TEXT
|
||||
@ECHO OFF
|
||||
@"%~dp0ruby.exe" "%~dpn0" %*
|
||||
TEXT
|
||||
elsif bindir.downcase.start_with? rb_topdir.downcase
|
||||
# stub within ruby folder, but not standard bin. Portable
|
||||
elsif bindir.downcase.start_with?((RbConfig::TOPDIR || File.dirname(rb_bindir)).downcase)
|
||||
# stub within ruby folder, but not standard bin. Not portable
|
||||
require 'pathname'
|
||||
from = Pathname.new bindir
|
||||
to = Pathname.new "#{rb_topdir}/bin"
|
||||
to = Pathname.new rb_bindir
|
||||
rel = to.relative_path_from from
|
||||
<<-TEXT
|
||||
@ECHO OFF
|
||||
@"%~dp0#{rel}/ruby.exe" "%~dpn0" %*
|
||||
TEXT
|
||||
else
|
||||
# outside ruby folder, maybe -user-install or bundler. Portable, but ruby
|
||||
# is dependent on PATH
|
||||
# outside ruby folder, maybe -user-install or bundler. Portable
|
||||
<<-TEXT
|
||||
@ECHO OFF
|
||||
@ruby.exe "%~dpn0" %*
|
||||
|
|
|
@ -152,7 +152,34 @@ class Gem::RequestSet
|
|||
@prerelease = options[:prerelease]
|
||||
|
||||
requests = []
|
||||
download_queue = Queue.new
|
||||
|
||||
# Create a thread-safe list of gems to download
|
||||
sorted_requests.each do |req|
|
||||
download_queue << req
|
||||
end
|
||||
|
||||
# Create N threads in a pool, have them download all the gems
|
||||
threads = Gem.configuration.concurrent_downloads.times.map do
|
||||
# When a thread pops this item, it knows to stop running. The symbol
|
||||
# is queued here so that there will be one symbol per thread.
|
||||
download_queue << :stop
|
||||
|
||||
Thread.new do
|
||||
# The pop method will block waiting for items, so the only way
|
||||
# to stop a thread from running is to provide a final item that
|
||||
# means the thread should stop.
|
||||
while req = download_queue.pop
|
||||
break if req == :stop
|
||||
req.spec.download options unless req.installed?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Wait for all the downloads to finish before continuing
|
||||
threads.each(&:value)
|
||||
|
||||
# Install requested gems after they have been downloaded
|
||||
sorted_requests.each do |req|
|
||||
if req.installed? then
|
||||
req.spec.spec.build_extensions
|
||||
|
|
|
@ -84,11 +84,7 @@ class Gem::Resolver::Specification
|
|||
def install options = {}
|
||||
require 'rubygems/installer'
|
||||
|
||||
destination = options[:install_dir] || Gem.dir
|
||||
|
||||
Gem.ensure_gem_subdirectories destination
|
||||
|
||||
gem = source.download spec, destination
|
||||
gem = download options
|
||||
|
||||
installer = Gem::Installer.at gem, options
|
||||
|
||||
|
@ -97,6 +93,14 @@ class Gem::Resolver::Specification
|
|||
@spec = installer.install
|
||||
end
|
||||
|
||||
def download options
|
||||
dir = options[:install_dir] || Gem.dir
|
||||
|
||||
Gem.ensure_gem_subdirectories dir
|
||||
|
||||
source.download spec, dir
|
||||
end
|
||||
|
||||
##
|
||||
# Returns true if this specification is installable on this platform.
|
||||
|
||||
|
|
|
@ -29,6 +29,24 @@ class Gem::Security::Signer
|
|||
|
||||
attr_reader :digest_name # :nodoc:
|
||||
|
||||
##
|
||||
# Attemps to re-sign an expired cert with a given private key
|
||||
def self.re_sign_cert(expired_cert, expired_cert_path, private_key)
|
||||
return unless expired_cert.not_after < Time.now
|
||||
|
||||
expiry = expired_cert.not_after.strftime('%Y%m%d%H%M%S')
|
||||
expired_cert_file = "#{File.basename(expired_cert_path)}.expired.#{expiry}"
|
||||
new_expired_cert_path = File.join(Gem.user_home, ".gem", expired_cert_file)
|
||||
|
||||
Gem::Security.write(expired_cert, new_expired_cert_path)
|
||||
|
||||
re_signed_cert = Gem::Security.re_sign(expired_cert, private_key)
|
||||
|
||||
Gem::Security.write(re_signed_cert, expired_cert_path)
|
||||
|
||||
yield(expired_cert_path, new_expired_cert_path) if block_given?
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a new signer with an RSA +key+ or path to a key, and a certificate
|
||||
# +chain+ containing X509 certificates, encoding certificates or paths to
|
||||
|
|
|
@ -512,11 +512,10 @@ class Gem::StreamUI
|
|||
# Return a download reporter object chosen from the current verbosity
|
||||
|
||||
def download_reporter(*args)
|
||||
case Gem.configuration.verbose
|
||||
when nil, false
|
||||
if [nil, false].include?(Gem.configuration.verbose) || !@outs.tty?
|
||||
SilentDownloadReporter.new(@outs, *args)
|
||||
else
|
||||
VerboseDownloadReporter.new(@outs, *args)
|
||||
ThreadedDownloadReporter.new(@outs, *args)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -553,9 +552,11 @@ class Gem::StreamUI
|
|||
end
|
||||
|
||||
##
|
||||
# A progress reporter that prints out messages about the current progress.
|
||||
# A progress reporter that behaves nicely with threaded downloading.
|
||||
|
||||
class VerboseDownloadReporter
|
||||
class ThreadedDownloadReporter
|
||||
|
||||
MUTEX = Mutex.new
|
||||
|
||||
##
|
||||
# The current file name being displayed
|
||||
|
@ -563,71 +564,43 @@ class Gem::StreamUI
|
|||
attr_reader :file_name
|
||||
|
||||
##
|
||||
# The total bytes in the file
|
||||
|
||||
attr_reader :total_bytes
|
||||
|
||||
##
|
||||
# The current progress (0 to 100)
|
||||
|
||||
attr_reader :progress
|
||||
|
||||
##
|
||||
# Creates a new verbose download reporter that will display on
|
||||
# Creates a new threaded download reporter that will display on
|
||||
# +out_stream+. The other arguments are ignored.
|
||||
|
||||
def initialize(out_stream, *args)
|
||||
@out = out_stream
|
||||
@progress = 0
|
||||
end
|
||||
|
||||
##
|
||||
# Tells the download reporter that the +file_name+ is being fetched and
|
||||
# contains +total_bytes+.
|
||||
# Tells the download reporter that the +file_name+ is being fetched.
|
||||
# The other arguments are ignored.
|
||||
|
||||
def fetch(file_name, total_bytes)
|
||||
@file_name = file_name
|
||||
@total_bytes = total_bytes.to_i
|
||||
@units = @total_bytes.zero? ? 'B' : '%'
|
||||
|
||||
update_display(false)
|
||||
def fetch(file_name, *args)
|
||||
if @file_name.nil?
|
||||
@file_name = file_name
|
||||
locked_puts "Fetching #{@file_name}"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Updates the verbose download reporter for the given number of +bytes+.
|
||||
# Updates the threaded download reporter for the given number of +bytes+.
|
||||
|
||||
def update(bytes)
|
||||
new_progress = if @units == 'B' then
|
||||
bytes
|
||||
else
|
||||
((bytes.to_f * 100) / total_bytes.to_f).ceil
|
||||
end
|
||||
|
||||
return if new_progress == @progress
|
||||
|
||||
@progress = new_progress
|
||||
update_display
|
||||
# Do nothing.
|
||||
end
|
||||
|
||||
##
|
||||
# Indicates the download is complete.
|
||||
|
||||
def done
|
||||
@progress = 100 if @units == '%'
|
||||
update_display(true, true)
|
||||
# Do nothing.
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_display(show_progress = true, new_line = false) # :nodoc:
|
||||
return unless @out.tty?
|
||||
|
||||
if show_progress then
|
||||
@out.print "\rFetching: %s (%3d%s)" % [@file_name, @progress, @units]
|
||||
else
|
||||
@out.print "Fetching: %s" % @file_name
|
||||
def locked_puts(message)
|
||||
MUTEX.synchronize do
|
||||
@out.puts message
|
||||
end
|
||||
@out.puts if new_line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,14 +9,16 @@ end
|
|||
class TestGemCommandsCertCommand < Gem::TestCase
|
||||
|
||||
ALTERNATE_CERT = load_cert 'alternate'
|
||||
EXPIRED_PUBLIC_CERT = load_cert 'expired'
|
||||
|
||||
ALTERNATE_KEY_FILE = key_path 'alternate'
|
||||
PRIVATE_KEY_FILE = key_path 'private'
|
||||
PUBLIC_KEY_FILE = key_path 'public'
|
||||
|
||||
ALTERNATE_CERT_FILE = cert_path 'alternate'
|
||||
CHILD_CERT_FILE = cert_path 'child'
|
||||
PUBLIC_CERT_FILE = cert_path 'public'
|
||||
ALTERNATE_CERT_FILE = cert_path 'alternate'
|
||||
CHILD_CERT_FILE = cert_path 'child'
|
||||
PUBLIC_CERT_FILE = cert_path 'public'
|
||||
EXPIRED_PUBLIC_CERT_FILE = cert_path 'expired'
|
||||
|
||||
def setup
|
||||
super
|
||||
|
@ -582,6 +584,37 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis
|
|||
assert_equal expected, @ui.error
|
||||
end
|
||||
|
||||
def test_execute_re_sign
|
||||
gem_path = File.join Gem.user_home, ".gem"
|
||||
Dir.mkdir gem_path
|
||||
|
||||
path = File.join @tempdir, 'cert.pem'
|
||||
Gem::Security.write EXPIRED_PUBLIC_CERT, path, 0600
|
||||
|
||||
assert_equal '/CN=nobody/DC=example', EXPIRED_PUBLIC_CERT.issuer.to_s
|
||||
|
||||
tmp_expired_cert_file = File.join(Dir.tmpdir, File.basename(EXPIRED_PUBLIC_CERT_FILE))
|
||||
File.write(tmp_expired_cert_file, File.read(EXPIRED_PUBLIC_CERT_FILE))
|
||||
|
||||
@cmd.handle_options %W[
|
||||
--private-key #{PRIVATE_KEY_FILE}
|
||||
--certificate #{tmp_expired_cert_file}
|
||||
--re-sign
|
||||
]
|
||||
|
||||
use_ui @ui do
|
||||
@cmd.execute
|
||||
end
|
||||
|
||||
expected_path = File.join(gem_path, "#{File.basename(tmp_expired_cert_file)}.expired")
|
||||
|
||||
assert_match(
|
||||
/INFO: Your certificate #{tmp_expired_cert_file} has been re-signed\nINFO: Your expired certificate will be located at: #{expected_path}\.[0-9]+/,
|
||||
@ui.output
|
||||
)
|
||||
assert_equal '', @ui.error
|
||||
end
|
||||
|
||||
def test_handle_options
|
||||
@cmd.handle_options %W[
|
||||
--add #{PUBLIC_CERT_FILE}
|
||||
|
|
|
@ -156,14 +156,14 @@ class TestGemStreamUI < Gem::TestCase
|
|||
def test_download_reporter_anything
|
||||
@cfg.verbose = 0
|
||||
reporter = @sui.download_reporter
|
||||
assert_kind_of Gem::StreamUI::VerboseDownloadReporter, reporter
|
||||
assert_kind_of Gem::StreamUI::ThreadedDownloadReporter, reporter
|
||||
end
|
||||
|
||||
def test_verbose_download_reporter
|
||||
def test_threaded_download_reporter
|
||||
@cfg.verbose = true
|
||||
reporter = @sui.download_reporter
|
||||
reporter.fetch 'a.gem', 1024
|
||||
assert_equal "Fetching: a.gem", @out.string
|
||||
assert_equal "Fetching a.gem\n", @out.string
|
||||
end
|
||||
|
||||
def test_verbose_download_reporter_progress
|
||||
|
@ -171,7 +171,7 @@ class TestGemStreamUI < Gem::TestCase
|
|||
reporter = @sui.download_reporter
|
||||
reporter.fetch 'a.gem', 1024
|
||||
reporter.update 512
|
||||
assert_equal "Fetching: a.gem\rFetching: a.gem ( 50%)", @out.string
|
||||
assert_equal "Fetching a.gem\n", @out.string
|
||||
end
|
||||
|
||||
def test_verbose_download_reporter_progress_once
|
||||
|
@ -180,7 +180,7 @@ class TestGemStreamUI < Gem::TestCase
|
|||
reporter.fetch 'a.gem', 1024
|
||||
reporter.update 510
|
||||
reporter.update 512
|
||||
assert_equal "Fetching: a.gem\rFetching: a.gem ( 50%)", @out.string
|
||||
assert_equal "Fetching a.gem\n", @out.string
|
||||
end
|
||||
|
||||
def test_verbose_download_reporter_progress_complete
|
||||
|
@ -189,7 +189,7 @@ class TestGemStreamUI < Gem::TestCase
|
|||
reporter.fetch 'a.gem', 1024
|
||||
reporter.update 510
|
||||
reporter.done
|
||||
assert_equal "Fetching: a.gem\rFetching: a.gem ( 50%)\rFetching: a.gem (100%)\n", @out.string
|
||||
assert_equal "Fetching a.gem\n", @out.string
|
||||
end
|
||||
|
||||
def test_verbose_download_reporter_progress_nil_length
|
||||
|
@ -198,7 +198,7 @@ class TestGemStreamUI < Gem::TestCase
|
|||
reporter.fetch 'a.gem', nil
|
||||
reporter.update 1024
|
||||
reporter.done
|
||||
assert_equal "Fetching: a.gem\rFetching: a.gem (1024B)\rFetching: a.gem (1024B)\n", @out.string
|
||||
assert_equal "Fetching a.gem\n", @out.string
|
||||
end
|
||||
|
||||
def test_verbose_download_reporter_progress_zero_length
|
||||
|
@ -207,7 +207,7 @@ class TestGemStreamUI < Gem::TestCase
|
|||
reporter.fetch 'a.gem', 0
|
||||
reporter.update 1024
|
||||
reporter.done
|
||||
assert_equal "Fetching: a.gem\rFetching: a.gem (1024B)\rFetching: a.gem (1024B)\n", @out.string
|
||||
assert_equal "Fetching a.gem\n", @out.string
|
||||
end
|
||||
|
||||
def test_verbose_download_reporter_no_tty
|
||||
|
|
Loading…
Add table
Reference in a new issue