Class: Chef::Provider::Package::Rubygems

Inherits:
Chef::Provider::Package show all
Includes:
Mixin::GetSourceFromPackage
Defined in:
lib/chef/provider/package/rubygems.rb

Defined Under Namespace

Classes: AlternateGemEnvironment, CurrentGemEnvironment, GemEnvironment

Instance Attribute Summary collapse

Attributes inherited from Chef::Provider

#action, #cookbook_name, #current_resource, #new_resource, #recipe_name, #run_context

Instance Method Summary collapse

Methods inherited from Chef::Provider::Package

#action_lock, #action_unlock, #as_array, #check_resource_semantics!, #define_resource_requirements, #expand_options, #get_preseed_file, #have_any_matching_version?, #lock_package, #multipackage_api_adapter, #options, #package_locked, #preseed_package, #preseed_resource, #reconfig_package, #removing_package?, #target_version_already_installed?, #unlock_package

Methods included from Mixin::SubclassDirective

#subclass_directive

Methods included from Mixin::ShellOut

#a_to_s, #clean_array, #shell_out, #shell_out!, #shell_out_compact, #shell_out_compact!, #shell_out_compact_timeout, #shell_out_compact_timeout!, #shell_out_with_systems_locale, #shell_out_with_systems_locale!

Methods included from Mixin::PathSanity

#enforce_path_sanity, #sanitized_path

Methods inherited from Chef::Provider

action, #action_nothing, #check_resource_semantics!, #compile_and_converge_action, #converge_by, #converge_if_changed, #define_resource_requirements, #events, include_resource_dsl?, include_resource_dsl_module, #node, #process_resource_requirements, provides, provides?, #requirements, #resource_collection, #resource_updated?, #run_action, #set_updated_status, supports?, use_inline_resources, #whyrun_mode?, #whyrun_supported?

Methods included from Mixin::Provides

#provided_as, #provides, #provides?

Methods included from Mixin::DescendantsTracker

#descendants, descendants, direct_descendants, #direct_descendants, find_descendants_by_name, #find_descendants_by_name, #inherited, store_inherited

Methods included from Mixin::LazyModuleInclude

#descendants, #include, #included

Methods included from Mixin::NotifyingBlock

#notifying_block, #subcontext_block

Methods included from DSL::DeclareResource

#build_resource, #declare_resource, #delete_resource, #delete_resource!, #edit_resource, #edit_resource!, #find_resource, #find_resource!, #with_run_context

Methods included from Mixin::PowershellOut

#powershell_out, #powershell_out!

Methods included from Mixin::WindowsArchitectureHelper

#assert_valid_windows_architecture!, #disable_wow64_file_redirection, #forced_32bit_override_required?, #is_i386_process_on_x86_64_windows?, #node_supports_windows_architecture?, #node_windows_architecture, #restore_wow64_file_redirection, #valid_windows_architecture?, #with_os_architecture, #wow64_architecture_override_required?, #wow64_directory

Methods included from DSL::PlatformIntrospection

#docker?, #platform?, #platform_family?, #value_for_platform, #value_for_platform_family

Constructor Details

#initialize(new_resource, run_context = nil) ⇒ Rubygems

Returns a new instance of Rubygems.



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
# File 'lib/chef/provider/package/rubygems.rb', line 363

def initialize(new_resource, run_context = nil)
  super
  @cleanup_gem_env = true
  if new_resource.gem_binary
    if new_resource.options && new_resource.options.is_a?(Hash)
      msg =  "options cannot be given as a hash when using an explicit gem_binary\n"
      msg << "in #{new_resource} from #{new_resource.source_line}"
      raise ArgumentError, msg
    end
    @gem_env = AlternateGemEnvironment.new(new_resource.gem_binary)
    Chef::Log.debug("#{new_resource} using gem '#{new_resource.gem_binary}'")
  elsif is_omnibus? && (!new_resource.instance_of? Chef::Resource::ChefGem)
    # Opscode Omnibus - The ruby that ships inside omnibus is only used for Chef
    # Default to installing somewhere more functional
    if new_resource.options && new_resource.options.is_a?(Hash)
      msg = [
        "Gem options must be passed to gem_package as a string instead of a hash when",
        "using this installation of Chef because it runs with its own packaged Ruby. A hash",
        "may only be used when installing a gem to the same Ruby installation that Chef is",
        "running under.  See https://docs.chef.io/resource_gem_package.html for more information.",
        "Error raised at #{new_resource} from #{new_resource.source_line}",
      ].join("\n")
      raise ArgumentError, msg
    end
    gem_location = find_gem_by_path
    new_resource.gem_binary gem_location
    @gem_env = AlternateGemEnvironment.new(gem_location)
    Chef::Log.debug("#{new_resource} using gem '#{gem_location}'")
  else
    @gem_env = CurrentGemEnvironment.new
    @cleanup_gem_env = false
    Chef::Log.debug("#{new_resource} using gem from running ruby environment")
  end
end

Instance Attribute Details

#cleanup_gem_envObject (readonly)

Returns the value of attribute cleanup_gem_env.



352
353
354
# File 'lib/chef/provider/package/rubygems.rb', line 352

def cleanup_gem_env
  @cleanup_gem_env
end

#gem_envObject (readonly)

Returns the value of attribute gem_env.



351
352
353
# File 'lib/chef/provider/package/rubygems.rb', line 351

def gem_env
  @gem_env
end

Instance Method Details

#all_installed_versionsObject



466
467
468
469
470
# File 'lib/chef/provider/package/rubygems.rb', line 466

def all_installed_versions
  @all_installed_versions ||= begin
                                @gem_env.installed_versions(Gem::Dependency.new(gem_dependency.name, ">= 0"))
                              end
end

#candidate_versionObject



494
495
496
497
498
499
500
501
502
# File 'lib/chef/provider/package/rubygems.rb', line 494

def candidate_version
  @candidate_version ||= begin
                          if source_is_remote?
                            @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
                          else
                            @gem_env.candidate_version_from_file(gem_dependency, new_resource.source).to_s
                          end
                        end
end

#cleanup_after_convergeObject



487
488
489
490
491
492
# File 'lib/chef/provider/package/rubygems.rb', line 487

def cleanup_after_converge
  if @cleanup_gem_env
    logger.debug { "#{new_resource} resetting gem environment to default" }
    Gem.clear_paths
  end
end

#current_versionObject



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/chef/provider/package/rubygems.rb', line 436

def current_version
  # rubygems 2.6.3 ensures that gem lists are sorted newest first
  pos = if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("2.6.3")
          :first
        else
          :last
        end

  # If one or more matching versions are installed, the newest of them
  # is the current version
  if !matching_installed_versions.empty?
    gemspec = matching_installed_versions.send(pos)
    logger.debug { "#{new_resource} found installed gem #{gemspec.name} version #{gemspec.version} matching #{gem_dependency}" }
    gemspec
    # If no version matching the requirements exists, the latest installed
    # version is the current version.
  elsif !all_installed_versions.empty?
    gemspec = all_installed_versions.send(pos)
    logger.debug { "#{new_resource} newest installed version of gem #{gemspec.name} is #{gemspec.version}" }
    gemspec
  else
    logger.debug { "#{new_resource} no installed version found for #{gem_dependency}" }
    nil
  end
end

#find_gem_by_pathObject



412
413
414
415
416
417
418
# File 'lib/chef/provider/package/rubygems.rb', line 412

def find_gem_by_path
  Chef::Log.debug("#{new_resource} searching for 'gem' binary in path: #{ENV['PATH']}")
  separator = ::File::ALT_SEPARATOR ? ::File::ALT_SEPARATOR : ::File::SEPARATOR
  path_to_first_gem = ENV["PATH"].split(::File::PATH_SEPARATOR).find { |path| ::File.exist?(path + separator + "gem") }
  raise Chef::Exceptions::FileNotFound, "Unable to find 'gem' binary in path: #{ENV['PATH']}" if path_to_first_gem.nil?
  path_to_first_gem + separator + "gem"
end

#gem_binary_pathObject



534
535
536
# File 'lib/chef/provider/package/rubygems.rb', line 534

def gem_binary_path
  new_resource.gem_binary || "gem"
end

#gem_dependencyObject



420
421
422
# File 'lib/chef/provider/package/rubygems.rb', line 420

def gem_dependency
  Gem::Dependency.new(new_resource.package_name, new_resource.version)
end

#gem_sourcesObject



472
473
474
475
476
# File 'lib/chef/provider/package/rubygems.rb', line 472

def gem_sources
  srcs = [ new_resource.source ]
  srcs << Chef::Config[:rubygems_url] if new_resource.include_default_source
  srcs.flatten.compact
end

#install_package(name, version) ⇒ Object

Installs the gem, using either the gems API or shelling out to gem according to the following criteria:

  1. Use gems API (Gem::DependencyInstaller) by default

  2. shell out to ‘gem install` when a String of options is given

  3. use gems API with options if a hash of options is given



515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
# File 'lib/chef/provider/package/rubygems.rb', line 515

def install_package(name, version)
  if source_is_remote? && new_resource.gem_binary.nil?
    if new_resource.options.nil?
      @gem_env.install(gem_dependency, sources: gem_sources)
    elsif new_resource.options.is_a?(Hash)
      options = new_resource.options
      options[:sources] = gem_sources
      @gem_env.install(gem_dependency, options)
    else
      install_via_gem_command(name, version)
    end
  elsif new_resource.gem_binary.nil?
    @gem_env.install(new_resource.source)
  else
    install_via_gem_command(name, version)
  end
  true
end

#install_via_gem_command(name, version) ⇒ Object



538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
# File 'lib/chef/provider/package/rubygems.rb', line 538

def install_via_gem_command(name, version)
  src = []
  if new_resource.source.is_a?(String) && new_resource.source =~ /\.gem$/i
    name = new_resource.source
  else
    src << "--clear-sources" if new_resource.clear_sources
    src += gem_sources.map { |s| "--source=#{s}" }
  end
  src_str = src.empty? ? "" : " #{src.join(" ")}"
  if !version.nil? && !version.empty?
    shell_out_with_timeout!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src_str}#{opts}", env: nil)
  else
    shell_out_with_timeout!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src_str}#{opts}", env: nil)
  end
end

#is_omnibus?Boolean

Returns:

  • (Boolean)


398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/chef/provider/package/rubygems.rb', line 398

def is_omnibus?
  if RbConfig::CONFIG["bindir"] =~ %r{/(opscode|chef|chefdk)/embedded/bin}
    Chef::Log.debug("#{new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
    # Omnibus installs to a static path because of linking on unix, find it.
    true
  elsif RbConfig::CONFIG["bindir"].sub(/^[\w]:/, "") == "/opscode/chef/embedded/bin"
    Chef::Log.debug("#{new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
    # windows, with the drive letter removed
    true
  else
    false
  end
end

#load_current_resourceObject



478
479
480
481
482
483
484
485
# File 'lib/chef/provider/package/rubygems.rb', line 478

def load_current_resource
  @current_resource = Chef::Resource::Package::GemPackage.new(new_resource.name)
  current_resource.package_name(new_resource.package_name)
  if current_spec = current_version
    current_resource.version(current_spec.version.to_s)
  end
  current_resource
end

#loggerObject



354
355
356
# File 'lib/chef/provider/package/rubygems.rb', line 354

def logger
  Chef::Log.logger
end

#matching_installed_versionsObject



462
463
464
# File 'lib/chef/provider/package/rubygems.rb', line 462

def matching_installed_versions
  @matching_installed_versions ||= @gem_env.installed_versions(gem_dependency)
end

#purge_package(name, version) ⇒ Object



580
581
582
# File 'lib/chef/provider/package/rubygems.rb', line 580

def purge_package(name, version)
  remove_package(name, version)
end

#remove_package(name, version) ⇒ Object



558
559
560
561
562
563
564
565
566
567
568
569
570
# File 'lib/chef/provider/package/rubygems.rb', line 558

def remove_package(name, version)
  if new_resource.gem_binary.nil?
    if new_resource.options.nil?
      @gem_env.uninstall(name, version)
    elsif new_resource.options.is_a?(Hash)
      @gem_env.uninstall(name, version, new_resource.options)
    else
      uninstall_via_gem_command(name, version)
    end
  else
    uninstall_via_gem_command(name, version)
  end
end

#source_is_remote?Boolean

Returns:

  • (Boolean)


424
425
426
427
428
429
430
431
432
433
434
# File 'lib/chef/provider/package/rubygems.rb', line 424

def source_is_remote?
  return true if new_resource.source.nil?
  return true if new_resource.source.is_a?(Array)
  scheme = URI.parse(new_resource.source).scheme
  # URI.parse gets confused by MS Windows paths with forward slashes.
  scheme = nil if scheme =~ /^[a-z]$/
  %w{http https}.include?(scheme)
rescue URI::InvalidURIError
  Chef::Log.debug("#{new_resource} failed to parse source '#{new_resource.source}' as a URI, assuming a local path")
  false
end

#uninstall_via_gem_command(name, version) ⇒ Object



572
573
574
575
576
577
578
# File 'lib/chef/provider/package/rubygems.rb', line 572

def uninstall_via_gem_command(name, version)
  if version
    shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", env: nil)
  else
    shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", env: nil)
  end
end

#upgrade_package(name, version) ⇒ Object



554
555
556
# File 'lib/chef/provider/package/rubygems.rb', line 554

def upgrade_package(name, version)
  install_package(name, version)
end

#version_requirement_satisfied?(current_version, new_version) ⇒ Boolean

Returns:

  • (Boolean)


504
505
506
507
# File 'lib/chef/provider/package/rubygems.rb', line 504

def version_requirement_satisfied?(current_version, new_version)
  return false unless current_version && new_version
  Gem::Requirement.new(new_version).satisfied_by?(Gem::Version.new(current_version))
end