class Bundler::Patch::CLI

Public Class Methods

execute() click to toggle source
# File lib/bundler/patch/cli.rb, line 9
def self.execute
  original_command = ARGV.join(' ')

  opts = Slop.parse! do
    banner "Bundler Patch Version #{Bundler::Patch::VERSION}\nUsage: bundle patch [options] [gems-to-update]\n\nbundler-patch attempts to update gems conservatively.\n"
    on '-m', '--minor', 'Prefer update to the latest minor.patch version.'
    on '-n', '--minimal', 'Prefer minimal version updates over most recent patch (or minor if -m used).'
    on '-s', '--strict', 'Restrict any gem to be upgraded past most recent patch (or minor if -m used).'
    on '-l', '--list', 'List vulnerable gems and new version target. No updates will be performed.'
    on '-v', '--vulnerable-gems-only', 'Only update vulnerable gems.'
    on '-a=', '--advisory-db-path=', 'Optional custom advisory db path. `gems` dir will be appended to this path.'
    on '-d=', '--ruby-advisory-db-path=', 'Optional path for ruby advisory db. `gems` dir will be appended to this path.'
    on '-r', '--ruby', 'Update Ruby version in related files.'
    on '--rubies=', 'Supported Ruby versions. Comma delimited or multiple switches.', as: Array, delimiter: ','
    on '-g=', '--gemfile=', 'Optional Gemfile to execute against. Defaults to Gemfile in current directory.'
    on '--use_target_ruby', 'Optionally attempt to use Ruby version of target bundle specified in --gemfile.'
    on '-h', 'Show this help'
    on '--help', 'Show README.md'

    # will be stripped in help display and normalized to hyphenated options
    on '--vulnerable_gems_only'
    on '--advisory_db_path='
    on '--ruby_advisory_db_path='
    on '-p', '--prefer_minimal'
    on '--minor_preferred'
    on '--strict_updates'
  end

  options = opts.to_hash
  options[:gems_to_update] = ARGV
  options[:original_command] = original_command
  STDERR.puts options.inspect if ENV['DEBUG']

  show_help(opts) if options[:h]
  show_readme if ARGV.include?('help') || options[:help]

  CLI.new.patch(options)
end
new() click to toggle source
# File lib/bundler/patch/cli.rb, line 59
def initialize
  @no_vulns_message = 'No known vulnerabilities to update.'
end
show_help(slop) click to toggle source
# File lib/bundler/patch/cli.rb, line 48
def self.show_help(slop)
  slop.options.delete_if { |o| o.long =~ /_/ }
  puts slop
  exit
end
show_readme() click to toggle source
# File lib/bundler/patch/cli.rb, line 54
def self.show_readme
  Kernel.exec "less '#{File.expand_path('../../../../README.md', __FILE__)}'"
  exit
end

Public Instance Methods

launch_target_bundler_patch(options) click to toggle source
# File lib/bundler/patch/cli.rb, line 80
def launch_target_bundler_patch(options)
  tb = options[:target]
  ruby = tb.ruby_bin_exe
  tb.install_bundler_patch_in_target
  bundler_patch = File.join(tb.ruby_bin, 'bundler-patch')
  full_command = %Q{GEM_HOME="#{tb.gem_home}" "#{ruby}" "#{bundler_patch}" #{options[:original_command].gsub(/use_target_ruby/, '')}}
  result = shell_command(full_command)
  puts result[:stdout] unless ENV['BP_DEBUG']
end
patch(options={}) click to toggle source
# File lib/bundler/patch/cli.rb, line 63
def patch(options={})
  Bundler.ui = Bundler::UI::Shell.new

  options = Bundler::Patch::CLI::Options.new.normalize_options(options)

  tb = options[:target]
  if options[:use_target_ruby] && tb.target_ruby_is_different?
    launch_target_bundler_patch(options)
  else
    return list(options) if options[:list]

    patch_ruby(options) if options[:ruby]

    patch_gems(options)
  end
end

Private Instance Methods

conservative_update(gem_patches, options={}, bundler_def=nil) click to toggle source
# File lib/bundler/patch/cli.rb, line 147
def conservative_update(gem_patches, options={}, bundler_def=nil)
  prep = DefinitionPrep.new(bundler_def, gem_patches, options).tap { |p| p.prep }

  # update => true is very important, otherwise without any Gemfile changes, the installer
  # may end up concluding everything can be resolved locally, nothing is changing,
  # and then nothing is done. lib/bundler/cli/update.rb also hard-codes this.
  Bundler::Installer.install(options[:target].dir, prep.bundler_def, {'update' => true})
  Bundler.load.cache if Bundler.app_cache.exist?
end
list(options) click to toggle source
# File lib/bundler/patch/cli.rb, line 92
def list(options)
  gem_patches = AdvisoryConsolidator.new(options).vulnerable_gems

  if gem_patches.empty?
    Bundler.ui.info @no_vulns_message
  else
    Bundler.ui.info '' # extra line to separate from advisory db update text
    Bundler.ui.info 'Detected vulnerabilities:'
    Bundler.ui.info '-------------------------'
    Bundler.ui.info gem_patches.map(&:to_s).uniq.sort.join("\n")
  end
end
patch_gems(options) click to toggle source
# File lib/bundler/patch/cli.rb, line 110
def patch_gems(options)
  vulnerable_patches = AdvisoryConsolidator.new(options).patch_gemfile_and_get_gem_specs_to_patch
  requested_patches = (options.delete(:gems_to_update) || []).map { |gem_name| GemPatch.new(gem_name: gem_name) }

  all_gem_patches = GemsToPatchReconciler.new(vulnerable_patches, requested_patches).reconciled_patches
  all_gem_patches.push(*vulnerable_patches) if options[:vulnerable_gems_only] && all_gem_patches.empty?

  vulnerable_patches, warnings = vulnerable_patches.partition { |gp| !gp.new_version.nil? }

  unless warnings.empty?
    warnings.each do |gp|
      Bundler.ui.warn "* Could not attempt upgrade for #{gp.gem_name} from #{gp.old_version} to any patched versions " \
        + "#{gp.patched_versions.join(', ')}. Most often this is because a major version increment would be " \
        + "required and it's safer for a major version increase to be done manually."
    end
  end

  if vulnerable_patches.empty?
    Bundler.ui.info @no_vulns_message
  else
    vulnerable_patches.each do |gp|
      Bundler.ui.info "Attempting conservative update for vulnerable gem '#{gp.gem_name}': #{gp.old_version} => #{gp.new_version}"
    end
  end

  if all_gem_patches.empty?
    if options[:vulnerable_gems_only]
      return # nothing to do
    else
      Bundler.ui.info 'Updating all gems conservatively.'
    end
  else
    Bundler.ui.info "Updating '#{all_gem_patches.map(&:gem_name).join(' ')}' conservatively."
  end
  conservative_update(all_gem_patches, options)
end
patch_ruby(options) click to toggle source
# File lib/bundler/patch/cli.rb, line 105
def patch_ruby(options)
  supported = options[:rubies]
  RubyVersion.new(target_bundle: options[:target], patched_versions: supported).update
end